using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Text.RegularExpressions;

// ========================================================================
//
//                          StatementParser.cs
//
// Author: Sarah Nimitz                         Date: May 2, 2016
// Purpose: Take the text of a severe weather statement and parse for the
//          relevant information for use in other programs. This information
//          includes: event tracking number (ETN), publishing station, type
//          of warning, date of publication, time of expiration, geometry,
//          and movement of the relevant weather system. This uses the
//          SevereWeatherStatement and Warning objects.
//
//          Known Issues: Spanish Statements
//                              College of DuPage also published statements
//                              in Spanish that are not parsed properly.
//                        Human Error
//                              Some severe weather statements are not
//                              correctly formatted, causing them to not be
//                              parsed correctly.
// 
// ========================================================================
namespace NWSWarnings
{
    public class StatementParser
	{
        // ================================================================
        // Processes a severe weather statement published by the National
        // Weather Service and creates a SevereWeatherStatement object and
        // a Warning object, if appropriate, for reference.
        //
        // @params weatherStatement - The entirety of a published severe
        //                          weather statement.
        // @return newSWS - A SevereWeatherStatement object containing all
        //                  information parsed from the input statement. The
        //                  warning, if created, is accessibly by this
        //                  SevereWeatherStatement object.
        // ================================================================
        public SevereWeatherStatement ParseStatement(String weatherStatement)
        {
            // Text file broken up by \n\n
            // Each section parsed
            // Severe Weather Statment created and returned
            SevereWeatherStatement newSWS = null;
            Warning newWarn = null;
            DateTime stateTime = new DateTime(), warnTime = new DateTime();
            int warnETN = -1;
            string warnStation = null, stateStation = null;
            String[] warnMove = null;
            List<PointF> warnPoints = null;

            String[] sections = Regex.Split(weatherStatement, "\n\n");

            WarningType warnType = ParseType(sections[1]);
            stateTime = ParsePublish(sections[1]);

            switch (warnType)
            {
                // Severe Thunderstorm Warning parsing
                case (WarningType.SevereThunderstorm):
                    warnETN = ParseETN(sections[0]);
                    warnStation = ParseStation(sections[0]);
                    stateStation = warnStation;

                    for(int i = 0; i < sections.Length; i++)
                    {
                        if (sections[i].Contains(@"* UNTIL"))
                        {
                            warnTime = ParseTime(sections[i], stateTime);
                        }
                        else if (sections[i].Contains("LAT...LON"))
                        {
                            warnPoints = ParseVertices(sections[i]);
                            warnMove = ParseMovements(sections[i]);
                        }
                    }
                    if (warnMove == null || warnPoints == null || warnTime == null)
                    {
                        Console.WriteLine("Warning " + warnStation + warnETN.ToString("D4")
                            + " unable to be parsed correctly. Aborting warning creation.");
                    }
                    else
                    {
                        newWarn = new Warning(warnStation, warnETN, warnType);
                        newWarn.Time = warnTime;
                        newWarn.Start = stateTime;
                        newWarn.Vertices = warnPoints;
                        newWarn.Movement = warnMove;
                    }
                    break;

                // Tornado (Emergency/Reported) Warning parsing
                case (WarningType.Tornado):
                    warnETN = ParseETN(sections[0]);
                    warnStation = ParseStation(sections[0]);
                    stateStation = warnStation;

                    if (weatherStatement.Contains("TORNADO EMERGENCY"))
                        warnType = WarningType.TornadoEmergency;
                    else if (weatherStatement.Contains("REPORTED") || weatherStatement.Contains("CONFIRMED"))
                        warnType = WarningType.TornadoReported;

                    for (int i = 0; i < sections.Length; i++)
                    {
                        if (sections[i].Contains(@"* UNTIL"))
                        {
                            warnTime = ParseTime(sections[i], stateTime);
                        }
                        else if (sections[i].Contains("LAT...LON"))
                        {
                            warnPoints = ParseVertices(sections[i]);
                            warnMove = ParseMovements(sections[i]);
                        }
                    }

                    if (warnMove == null || warnPoints == null || warnTime == null)
                    {
                        Console.WriteLine("Warning " + warnStation + warnETN.ToString("D4")
                            + " unable to be parsed correctly. Aborting warning creation.");
                    }
                    else
                    {
                        newWarn = new Warning(warnStation, warnETN, warnType);
                        newWarn.Time = warnTime;
                        newWarn.Start = stateTime;
                        newWarn.Vertices = warnPoints;
                        newWarn.Movement = warnMove;
                    }
                    break;

                // Flash Flood Warning parsing
                case (WarningType.FlashFlood):
                    warnETN = ParseETN(sections[0]);
                    warnStation = ParseStation(sections[0]);
                    stateStation = warnStation;

                    for (int i = 0; i < sections.Length; i++)
                    {
                        if (sections[i].Contains(@"* UNTIL"))
                        {
                            warnTime = ParseTime(sections[i], stateTime);
                        }
                        else if (sections[i].Contains("LAT...LON"))
                        {
                            warnPoints = ParseVertices(sections[i]);
                        }
                    }

                    if (warnPoints == null || warnTime == null)
                    {
                        Console.WriteLine("Warning " + warnStation + warnETN.ToString("D4")
                            + " unable to be parsed correctly. Aborting warning creation.");
                    }
                    else
                    {
                        newWarn = new Warning(warnStation, warnETN, warnType);
                        newWarn.Time = warnTime;
                        newWarn.Start = stateTime;
                        newWarn.Vertices = warnPoints;
                    }
                    break;

                // Non-Warning Statements
                // (Either updates, unassociated, or errors)
                case (WarningType.Unknown):
                    string fileEnd = "";
                    string[] allFiles, allPortions, tempPortions;
                    int toParse = 0;
                    warnETN = ParseETN(sections[2]);
                    stateStation = ParseStation(sections[2]);

                    // Finds file 
                    if (!Directory.Exists(@".\Warnings\" + stateTime.Year.ToString("D4")))
                        Directory.CreateDirectory(@".\Warnings\" + stateTime.Year.ToString("D4"));
                    allFiles = Directory.GetFiles(@".\Warnings\" + stateTime.Year.ToString("D4") + @"\", warnStation + warnETN.ToString("D4") + "*.*");
                    foreach (string st in allFiles)
                    {

                        Match m = Regex.Match(st, "\\.([A-Z]{3})");
                        if (m.Success)
                        {
                            fileEnd = m.Groups[1].ToString();
                        }
                    }

                    switch (fileEnd)
                    {
                        case "TOR":
                            allPortions = Regex.Split(weatherStatement, @"\$\$");
                            if (weatherStatement.Contains("TORNADO EMERGENCY"))
                                warnType = WarningType.TornadoEmergency;
                            else if (weatherStatement.Contains("REPORTED") || weatherStatement.Contains("CONFIRMED"))
                                warnType = WarningType.TornadoReported;
                            else
                                warnType = WarningType.Tornado;

                            // More than one change statement
                            // Since first should only be cancellations and
                            // Movement etc info is the same, go to last one
                            // for extensions etc
                            if (allPortions.Length > 2)
                                toParse = allPortions.Length - 2;

                            tempPortions = Regex.Split(allPortions[toParse], "\n\n");

                            for (int i = 0; i < tempPortions.Length; i++)
                            {
                                if (tempPortions[i].Contains("UNTIL") || tempPortions[i].Contains("WILL EXPIRE AT"))
                                {
                                    if (!tempPortions[i].Contains("WATCH"))
                                        warnTime = ParseTime(tempPortions[i], stateTime);
                                }
                                else if (tempPortions[i].Contains("LAT...LON"))
                                {
                                    warnPoints = ParseVertices(tempPortions[i]);
                                    warnMove = ParseMovements(tempPortions[i]);
                                }
                            }
                            break;
                        case "SVR":
                            allPortions = Regex.Split(weatherStatement, @"\$\$");
                            warnType = WarningType.SevereThunderstorm;
                            // More than one change statement
                            // Since first should only be cancellations and
                            // Movement etc info is the same, go to last one
                            // for extensions etc
                            if (allPortions.Length > 2)
                                toParse = allPortions.Length - 2;

                            tempPortions = Regex.Split(allPortions[toParse], "\n\n");

                            for (int i = 0; i < tempPortions.Length; i++)
                            {
                                if (tempPortions[i].Contains("UNTIL") || tempPortions[i].Contains("WILL EXPIRE AT"))
                                {
                                    if (!tempPortions[i].Contains("WATCH"))
                                        warnTime = ParseTime(tempPortions[i], stateTime);
                                }
                                else if (tempPortions[i].Contains("LAT...LON"))
                                {
                                    warnPoints = ParseVertices(tempPortions[i]);
                                    warnMove = ParseMovements(tempPortions[i]);
                                }
                            }
                            break;
                        case "FFW":
                            allPortions = Regex.Split(weatherStatement, @"\$\$");
                            warnType = WarningType.FlashFlood;
                            // More than one change statement
                            // Since first should only be cancellations and
                            // Movement etc info is the same, go to last one
                            // for extensions etc
                            if (allPortions.Length > 2)
                                toParse = allPortions.Length - 2;

                            tempPortions = Regex.Split(allPortions[toParse], "\n\n");

                            for (int i = 0; i < tempPortions.Length; i++)
                            {
                                if (tempPortions[i].Contains("UNTIL") || tempPortions[i].Contains("WILL EXPIRE AT"))
                                {
                                    if (!tempPortions[i].Contains("WATCH"))
                                        warnTime = ParseTime(tempPortions[i], stateTime);
                                }
                                else if (tempPortions[i].Contains("LAT...LON"))
                                {
                                    warnPoints = ParseVertices(tempPortions[i]);
                                }
                            }
                            break;
                        case "":
                            // No file found
                            break;
                        default:
                            // Throw error - weird file ending
                            break;
                    }

                    // Get Warning by ETN/Station/ParseTime.Year
                    // Update Warning information
                    // Keywords: CANCELED, STILL IN EFFECT, WILL EXPIRE AT

                    break;

                case (WarningType.SpecialMarine):
                    Console.WriteLine("Special Marine Warning not supported. Aborting warning creation.");
                    break;

                default:
                    Console.WriteLine("Statement unparseable. Aborting warning creation.");
                    break;
            }

            if (newWarn == null)
            {
                newSWS = new SevereWeatherStatement(weatherStatement, stateTime, stateStation);
            }
            else
            {
                newSWS = new SevereWeatherStatement(newWarn, weatherStatement, stateTime);
                newWarn.Statements.Add(newSWS);
            }

            return newSWS;
        }

        // ================================================================
        // Parses the input section for the event tracking number (ETN) of
        // the warning referenced by the severe weather statement. This is
        // found by searching for the pattern .####. where each # represents
        // a digit 0-9.
        //
        // @params sectionID - The section of the severe weather statement
        //                      that should include the event tracking number
        //                      for the referenced warning.
        // @return toReturn - An integer representing the four-digit ETN of
        //                      the referenced warning.
        //                      toReturn = -1 on a failure.
        // ================================================================
        public int ParseETN(String sectionID)
        {
            // Pulls ETN from statement
            Match m1 = Regex.Match(sectionID, "\\.([0-9]{4})\\.");
            if (!m1.Success)
            {
                Console.WriteLine("ETN unable to be parsed. Aborting int creation.");
                return -1;
            }

            // Converts to number and returns
            int toReturn = Convert.ToUInt16(m1.Groups[1].ToString());
            if (toReturn == 0)
            {
                Console.WriteLine("ETN unable to be parsed. Aborting int creation.");
                return -1;
            }

            return toReturn;
        }

        // ================================================================
        // Parses the input section for the station that has published the
        // severe weather statement. This is found by searching for the
        // pattern .AAAA. where each A represents a capital letter A-Z.
        //
        // @params sectionID - The section of the severe weather statement
        //                      that should include the publishing station.
        // @return toReturn - A string representing the four-character
        //                      station that has published the statement.
        //                      toReturn = null on a failure.
        // ================================================================
        public String ParseStation(String sectionID)
        {
            // Pulls Station from statement
            Match m = Regex.Match(sectionID, "\\.([A-Z]{4})\\.");
            if (!m.Success)
            {
                Console.WriteLine("Station unable to be parsed. Aborting string creation.");
                return null;
            }

            string toReturn = m.Groups[1].ToString();

            return toReturn;
        }

        // ================================================================
        // Parses the input section to identify the type of warning that
        // is referenced by the severe weather statement. This is found by
        // searching the statement for several different key phrases.
        //
        // @params sectionType - The section of the severe weather statement
        //                          that should include the warning type.
        // @return value - The WarningType appropriate based on the text
        //                  contained within the input section.
        //                  WarningType.Unknown on a failure.
        // ================================================================
        public WarningType ParseType(String sectionType)
        {
            // This is a Severe Thunderstorm Warning
            if (sectionType.Contains("SEVERE THUNDERSTORM WARNING"))
                return WarningType.SevereThunderstorm;
            // This is a Tornado Warning
            else if (sectionType.Contains("TORNADO WARNING"))
                return WarningType.Tornado;
            // This is a Severe Weather Statement
            else if (sectionType.Contains("SEVERE WEATHER STATEMENT"))
                return WarningType.Unknown;
            // This is a Flash Flood Warning
            else if (sectionType.Contains("FLASH FLOOD WARNING"))
                return WarningType.FlashFlood;

            // An unknown warning type
            else
                return WarningType.Unknown;
        }

        // ================================================================
        // Parses the input section for the time at which the severe weather
        // statement was published.
        //
        // @params sectionPublish - The section of the severe weather statement
        //                          that should include the publication date.
        // @return initDay - A DateTime object representing when the statement
        //                      was published in Zulu time.
        //                      initDay = DateTime.MinValue on a failure.
        // ================================================================
        public DateTime ParsePublish(String sectionPublish)
        {
            int trueTime = -1, timeSec = -1, timeMin = -1, timeHr = -1, timeMon = -1, timeDay = -1, timeYr = -1;
            DateTime initDay = DateTime.MinValue;
            Regex timeFind = new Regex("([0-9]+ [PM + AM].*)");
            Match m = timeFind.Match(sectionPublish);

            // Tests for failure.
            if (!m.Success)
            {
                Console.WriteLine("Time unable to be parsed. Aborting DateTime creation.");
                return initDay;
            }

            String time = m.Groups[1].ToString();
            String[] timeBits = Regex.Split(time, "\\s");

            trueTime = Convert.ToUInt16(timeBits[0]);
            if (timeBits[1].Contains("PM") && trueTime != 1200)
                trueTime += 1200;
            else if (timeBits[1].Contains("AM") && trueTime >= 1200)
                trueTime -= 1200;

            switch (timeBits[2])
            {
                // Chamorro Time Zone
                case "ChST":
                    trueTime -= 1000;
                    break;
                // Hawaii-Aleutian Daylight Time
                case "HADT":
                    trueTime += 900;
                    break;
                // Hawaii-Aleutian Standard Time
                case "HAST":
                    trueTime += 1000;
                    break;
                // Hawaii Standard Time
                case "HST":
                    trueTime += 1000;
                    break;
                // Alaska Daylight Time
                case "AKDT":
                    trueTime += 800;
                    break;
                // Alaska Standard Time
                case "AKST":
                    trueTime += 900;
                    break;
                // Pacific Daylight Time (North America)
                case "PDT":
                    trueTime += 700;
                    break;
                // Pacific Standard Time (North America)
                case "PST":
                    trueTime += 800;
                    break;
                // Mountain Daylight Time (North America)
                case "MDT":
                    trueTime += 600;
                    break;
                // Mountain Standard Time (North America)
                case "MST":
                    trueTime += 700;
                    break;
                // Central Daylight Time (North America)
                case "CDT":
                    trueTime += 500;
                    break;
                // Central Standard Time (North America)
                case "CST":
                    trueTime += 600;
                    break;
                // Eastern Daylight Time (North America)
                case "EDT":
                    trueTime += 400;
                    break;
                // Eastern Standard Time (North America)
                case "EST":
                    trueTime += 500;
                    break;
                // Atlantic Daylight Time
                case "ADT":
                    trueTime += 300;
                    break;
                // Atlantic Standard Time
                case "AST":
                    trueTime += 400;
                    break;
                // Greenwich Mean Time (already Zulu)
                case "GMT":
                // Coordinated Universal Time (already Zulu)
                case "UCT":
                // Coordinated Universal Time (already Zulu)
                case "UTC":
                // Zulu Time
                case "Z":
                // Unknown Timezone, applying Zulu
                default:
                    break;
            }
            timeHr = trueTime / 100;
            timeMin = trueTime % 100;
            timeSec = 0;

            switch (timeBits[4])
            {
                case "JAN":
                    timeMon = 1;
                    break;
                case "FEB":
                    timeMon = 2;
                    break;
                case "MAR":
                    timeMon = 3;
                    break;
                case "APR":
                    timeMon = 4;
                    break;
                case "MAY":
                    timeMon = 5;
                    break;
                case "JUN":
                    timeMon = 6;
                    break;
                case "JUL":
                    timeMon = 7;
                    break;
                case "AUG":
                    timeMon = 8;
                    break;
                case "SEP":
                    timeMon = 9;
                    break;
                case "OCT":
                    timeMon = 10;
                    break;
                case "NOV":
                    timeMon = 11;
                    break;
                case "DEC":
                    timeMon = 12;
                    break;
                default:
                    Console.WriteLine("Unrecognized month. Aborting DateTime creation.");
                    return initDay;
            }
            timeDay = Convert.ToUInt16(timeBits[5]);
            timeYr = Convert.ToUInt16(timeBits[6]);
            if (timeHr >= 24)
            {
                timeHr -= 24;
                initDay = new DateTime(timeYr, timeMon, timeDay, timeHr, timeMin, timeSec, DateTimeKind.Utc);
                initDay = initDay.AddDays(1);
            }
            else if (timeHr < 0)
            {
                timeHr += 24;
                initDay = new DateTime(timeYr, timeMon, timeDay, timeHr, timeMin, timeSec, DateTimeKind.Utc);
                initDay = initDay.AddDays(-1);
            }
            else
            {
                initDay = new DateTime(timeYr, timeMon, timeDay, timeHr, timeMin, timeSec, DateTimeKind.Utc);
            }

            return initDay;
        }

        // ================================================================
        // Parses the input section for a specific point in time. This is
        // used both for the initially assigned expiration time of a warning
        // when a statement initialzes it, when a statement confirms its
        // expiration time, or when a statement write that a warning is
        // about to expire.
        //
        // @params sectionTime - The section of the severe weather statement
        //                          that the time must be parsed from.
        // @params publishTime - The time at which the severe weather statement
        //                          was published. This is used to get the
        //                          month, day, and year of the parsed time.
        // @return toReturn - A DateTime object representing the parsed time.
        //                      toReturn - DateTime.MinValue on a failure.
        // ================================================================
        public DateTime ParseTime(String sectionTime, DateTime publishTime)
        {
            // Recover time sections for parsing.
            Regex timeFind = new Regex("UNTIL ([0-9]+.*|MIDNIGHT.*|NOON.*)");
            Match m = timeFind.Match(sectionTime);
            String time = m.Groups[1].ToString();
            DateTime toReturn = DateTime.MinValue;

            // Tests for wrong section.

            if (!m.Success)
            {
                timeFind = new Regex("WILL EXPIRE AT ([0-9]+.*)");
                m = timeFind.Match(sectionTime);
                time = m.Groups[1].ToString();

                if (!m.Success)
                {
                    Console.WriteLine("Time unable to be parsed. Aborting DateTime creation.");
                    return toReturn;
                }
            }

            String[] timeBits = Regex.Split(time, "\\s");

            // Convert time to integer.
            int trueTime = -1, timeHr = -1, timeMin = -1, timeSec = -1;
            if (timeBits[0].Contains("MIDNIGHT"))
            {
                trueTime = 0000;

                // Switch-case to deal with potential timezone differences.
                // Should we condense to Zulu time?
                // Take into account international date line
                switch (timeBits[1])
                {
                    // Chamorro Time Zone
                    case "ChST":
                        trueTime -= 1000;
                        break;
                    // Hawaii-Aleutian Daylight Time
                    case "HADT":
                        trueTime += 900;
                        break;
                    // Hawaii-Aleutian Standard Time
                    case "HAST":
                        trueTime += 1000;
                        break;
                    // Hawaii Standard Time
                    case "HST":
                        trueTime += 1000;
                        break;
                    // Alaska Daylight Time
                    case "AKDT":
                        trueTime += 800;
                        break;
                    // Alaska Standard Time
                    case "AKST":
                        trueTime += 900;
                        break;
                    // Pacific Daylight Time (North America)
                    case "PDT":
                        trueTime += 700;
                        break;
                    // Pacific Standard Time (North America)
                    case "PST":
                        trueTime += 800;
                        break;
                    // Mountain Daylight Time (North America)
                    case "MDT":
                        trueTime += 600;
                        break;
                    // Mountain Standard Time (North America)
                    case "MST":
                        trueTime += 700;
                        break;
                    // Central Daylight Time (North America)
                    case "CDT":
                        trueTime += 500;
                        break;
                    // Central Standard Time (North America)
                    case "CST":
                        trueTime += 600;
                        break;
                    // Eastern Daylight Time (North America)
                    case "EDT":
                        trueTime += 400;
                        break;
                    // Eastern Standard Time (North America)
                    case "EST":
                        trueTime += 500;
                        break;
                    // Atlantic Daylight Time
                    case "ADT":
                        trueTime += 300;
                        break;
                    // Atlantic Standard Time
                    case "AST":
                        trueTime += 400;
                        break;
                    // Greenwich Mean Time (already Zulu)
                    case "GMT":
                    // Coordinated Universal Time (already Zulu)
                    case "UCT":
                    // Coordinated Universal Time (already Zulu)
                    case "UTC":
                    // Zulu Time
                    case "Z":
                    // Unknown Timezone, applying Zulu
                    default:
                        break;
                }
            }
            else if (timeBits[0].Contains("NOON"))
            {
                trueTime = 1200;

                // Switch-case to deal with potential timezone differences.
                // Should we condense to Zulu time?
                // Take into account international date line
                switch (timeBits[1])
                {
                    // Chamorro Time Zone
                    case "ChST":
                        trueTime -= 1000;
                        break;
                    // Hawaii-Aleutian Daylight Time
                    case "HADT":
                        trueTime += 900;
                        break;
                    // Hawaii-Aleutian Standard Time
                    case "HAST":
                        trueTime += 1000;
                        break;
                    // Hawaii Standard Time
                    case "HST":
                        trueTime += 1000;
                        break;
                    // Alaska Daylight Time
                    case "AKDT":
                        trueTime += 800;
                        break;
                    // Alaska Standard Time
                    case "AKST":
                        trueTime += 900;
                        break;
                    // Pacific Daylight Time (North America)
                    case "PDT":
                        trueTime += 700;
                        break;
                    // Pacific Standard Time (North America)
                    case "PST":
                        trueTime += 800;
                        break;
                    // Mountain Daylight Time (North America)
                    case "MDT":
                        trueTime += 600;
                        break;
                    // Mountain Standard Time (North America)
                    case "MST":
                        trueTime += 700;
                        break;
                    // Central Daylight Time (North America)
                    case "CDT":
                        trueTime += 500;
                        break;
                    // Central Standard Time (North America)
                    case "CST":
                        trueTime += 600;
                        break;
                    // Eastern Daylight Time (North America)
                    case "EDT":
                        trueTime += 400;
                        break;
                    // Eastern Standard Time (North America)
                    case "EST":
                        trueTime += 500;
                        break;
                    // Atlantic Daylight Time
                    case "ADT":
                        trueTime += 300;
                        break;
                    // Atlantic Standard Time
                    case "AST":
                        trueTime += 400;
                        break;
                    // Greenwich Mean Time (already Zulu)
                    case "GMT":
                    // Coordinated Universal Time (already Zulu)
                    case "UCT":
                    // Coordinated Universal Time (already Zulu)
                    case "UTC":
                    // Zulu Time
                    case "Z":
                    // Unknown Timezone, applying Zulu
                    default:
                        break;
                }
            }
            else
            {
                trueTime = Convert.ToUInt16(timeBits[0]);

                // Convert to 24-hour clock
                if (timeBits[1].Contains("PM") && trueTime != 1200)
                    trueTime += 1200;
                else if (timeBits[1].Contains("AM") && trueTime >= 1200)
                    trueTime -= 1200;

                // Switch-case to deal with potential timezone differences.
                // Should we condense to Zulu time?
                // Take into account international date line
                switch (timeBits[2])
                {
                    // Chamorro Time Zone
                    case "ChST":
                        trueTime -= 1000;
                        break;
                    // Hawaii-Aleutian Daylight Time
                    case "HADT":
                        trueTime += 900;
                        break;
                    // Hawaii-Aleutian Standard Time
                    case "HAST":
                        trueTime += 1000;
                        break;
                    // Hawaii Standard Time
                    case "HST":
                        trueTime += 1000;
                        break;
                    // Alaska Daylight Time
                    case "AKDT":
                        trueTime += 800;
                        break;
                    // Alaska Standard Time
                    case "AKST":
                        trueTime += 900;
                        break;
                    // Pacific Daylight Time (North America)
                    case "PDT":
                        trueTime += 700;
                        break;
                    // Pacific Standard Time (North America)
                    case "PST":
                        trueTime += 800;
                        break;
                    // Mountain Daylight Time (North America)
                    case "MDT":
                        trueTime += 600;
                        break;
                    // Mountain Standard Time (North America)
                    case "MST":
                        trueTime += 700;
                        break;
                    // Central Daylight Time (North America)
                    case "CDT":
                        trueTime += 500;
                        break;
                    // Central Standard Time (North America)
                    case "CST":
                        trueTime += 600;
                        break;
                    // Eastern Daylight Time (North America)
                    case "EDT":
                        trueTime += 400;
                        break;
                    // Eastern Standard Time (North America)
                    case "EST":
                        trueTime += 500;
                        break;
                    // Atlantic Daylight Time
                    case "ADT":
                        trueTime += 300;
                        break;
                    // Atlantic Standard Time
                    case "AST":
                        trueTime += 400;
                        break;
                    // Greenwich Mean Time (already Zulu)
                    case "GMT":
                    // Coordinated Universal Time (already Zulu)
                    case "UCT":
                    // Coordinated Universal Time (already Zulu)
                    case "UTC":
                    // Zulu Time
                    case "Z":
                    // Unknown Timezone, applying Zulu
                    default:
                        break;
                }
            }

            timeHr = trueTime / 100;
            timeMin = trueTime % 100;
            timeSec = 0;

            int dayDiff = 0;

            // This will account for any changes in date.
            // Since the warning always expires after
            // the publication, and since it is the next
            // occurence of the time (eg. within 24 hours)
            // all date changes will be accounted for.
            if (timeHr >= 24)
            {
                dayDiff += 1;
                timeHr -= 24;
            }
            else if (timeHr < 0)
            {
                dayDiff += -1;
                timeHr += 24;
            }

            toReturn = new DateTime(publishTime.Year, publishTime.Month, publishTime.Day, timeHr, timeMin, timeSec);
            toReturn = toReturn.AddDays(dayDiff);

            return toReturn;
        }

        // ================================================================
        // Parses the input section for the vertices represented by a list
        // of numbers, in order to identify the geometry of a warning. These
        // are then turned into PointF objects.
        //
        // @params sectionVertices - The section from which the vertices
        //                              must be parsed.
        // @return vertices - A list containing all parsed vertices.
        //                      vertices = Null on wrong section failure.
        //                      vertices = Empty list if unable to be parsed.
        // ================================================================
        public List<PointF> ParseVertices(String sectionVertices)
        {
            List<PointF> vertices = new List<PointF>();
            // Splits the given section into individual numbers
            String[] locationSections = Regex.Split(sectionVertices, @"\s+|\nTIME...MOT...LOC");

            // Test for wrong section
            if (locationSections[0] != "LAT...LON")
            {
                Console.WriteLine("Vertices unable to be parsed. Aborting list creation.");
                return null;
            }

            // Creates PointF for each coordinate until end of section
            int i = 1;
            while (i < locationSections.Length && locationSections[i] != "TIME...MOT...LOC")
            {
                PointF vertex = new PointF(Convert.ToSingle(locationSections[i]),
                    Convert.ToSingle(locationSections[i + 1]));
                vertices.Add(vertex);
                i = i + 2;
            }

            return vertices;
        }

        // ================================================================
        // Parses the input section for the movement of the weather system.
        // This will return an array that contain 5+ elements:
        //      * Time in Zulu the system was last observed
        //      * Degree (from North) of movement
        //      * Speed (in knots) the system is moving
        //      * Latitude of location
        //      * Longitude of location
        //      * [Any additional Lat/Lon pairs]
        //
        // @params sectionMovements - The section containing the information
        //                              on the movement of the weather system.
        // @return resStr - An array of strings representing all parts of
        //                  the weather system's movements.
        //                  resStr = empty array if there are not enough
        //                      elements within the input section to be
        //                      parsed through
        //                  resStr[i] = empty string if the information at
        //                      position i is unable to be parsed correctly
        // ================================================================
        public String[] ParseMovements (String sectionMovements)
        {
            Match here = Regex.Match(sectionMovements, "TIME...MOT...LOC\\s(.*)");
            String[] movStr = Regex.Split(here.Groups[1].ToString(), "\\s");
            String[] resStr = new String[movStr.Length];

            if (resStr.Length < 5)
            {
                Console.WriteLine("Movements unable to be parsed. Aborting array creation.");
                return resStr;
            }

            // Zulu time the system was observed
            Match forTime = Regex.Match(movStr[0], "[0-9]{4}");
            if (forTime.Success)
                resStr[0] = forTime.Groups[0].ToString();
            else
                resStr[0] = "";

            // Degree of system's bearing
            Match forDeg = Regex.Match(movStr[1], "[0-9]{3}");
            if (forTime.Success)
                resStr[1] = forDeg.Groups[0].ToString();
            else
                resStr[1] = "";

            // Speed of system in knots
            Match forKT = Regex.Match(movStr[2], "([0-9]*)KT");
            if (forTime.Success)
                resStr[2] = forKT.Groups[1].ToString();
            else
                resStr[2] = "";

            // Latitude/Longitude of system
            for (int i = 3; i < resStr.Length; i++)
            {
                resStr[i] = movStr[i];
            }

            return resStr;
        }
    }
}
