/// <summary>
        /// Updates the weather forecast.
        /// </summary>
        /// <param name="state">The state (required for TimerCallback)</param>
        private void UpdateWeatherForecast(Object state)
        {
            // timer callbacks can queue up and fire all at once. Of course in this case,
            // the interval is 4 hours, so we won’t see any callbacks queue up, but if the
            // interval was a few seconds, it would be possible.
            if (Interlocked.Exchange(ref _inTimerCallback, 1) != 0) {
                return;
            }

            try
            {
                // if someone disposed us, do nothing
                if (_disposed == 1)
                {
                    return;
                }

                // attempt to retrieve the weather forecast data
                WeatherForecastData updatedForecast = GetForecastForUpcomingMarket();

                if (updatedForecast != null)
                {
                    _forecastDataLock.AcquireWriterLock(Timeout.Infinite);
                    _forecastData = updatedForecast;
                    _forecastDataLock.ReleaseWriterLock();
                }

                // if we succeed, update in 4 hours, otherwise retry in 2 minutes
                TimeSpan newTimeSpan = ((updatedForecast != null) && updatedForecast.IsValid()) ?
                    new TimeSpan(4, 0, 0) : new TimeSpan(0, 2, 0);

                _timer.Change(newTimeSpan, newTimeSpan);
            }
            finally
            {
                Interlocked.Exchange(ref _inTimerCallback, 0);
            }
        }
        /// <summary>
        /// Gets the forecast for upcoming market day.
        /// </summary>
        /// <returns></returns>
        private static WeatherForecastData GetForecastForUpcomingMarket()
        {
            WeatherForecastData result;

            // determine the local date/time for the start of this Saturday's market
            int daysTilSaturday = DayOfWeek.Saturday - DateTime.Today.DayOfWeek;
            DateTime marketStarts = DateTime.Today.AddDays(daysTilSaturday).AddHours(8);

            // geographic location of Carpterter Village
            const decimal latitude = (decimal)35.8188612;
            const decimal longitude = (decimal)-78.8594969;

            // create an instance of the web service proxy
            var weatherFetcher = new ndfdXML {Proxy = WebRequest.DefaultWebProxy};

            // setup the parameters to tell the service what data we want
            // see http://www.nws.noaa.gov/forecasts/xml/docs/elementInputNames.php for parameters
            var weatherParams = new weatherParametersType
                                    {
                                        appt = true,
                                        icons = true,
                                        maxt = true,
                                        mint = true,
                                        pop12 = true,
                                        rh = true,
                                        wx = true,
                                        wwa = true,
                                        wspd = true
                                    };

            try
            {
                result = WeatherForecastData.Parse(weatherFetcher.NDFDgen(latitude, longitude, "time-series",
                    marketStarts.ToUniversalTime(), marketStarts.AddHours(4).ToUniversalTime(), weatherParams));
            }
            catch (Exception)
            {
                result = new WeatherForecastData();
            }

            return result;
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="WeatherForecastTimer"/> class.
        /// </summary>
        public WeatherForecastTimer()
        {
            // assume failure
            bool dispose = true;

            try
            {
                // register event handler on unload of the app domain to dispose the timer
                AppDomain appDomain = Thread.GetDomain();

                var onAppDomainUnload = new EventHandler(OnAppDomainUnload);
                appDomain.DomainUnload += onAppDomainUnload;
                _onAppDomainUnload = onAppDomainUnload;

                WeatherForecastData forecastData = GetForecastForUpcomingMarket();

                if (forecastData != null)
                {
                    _forecastDataLock.AcquireWriterLock(Timeout.Infinite);
                    _forecastData = forecastData;
                    _forecastDataLock.ReleaseWriterLock();
                }

                // if we get the forecast successfully, start the timer in 4 hours, otherwise
                // try again in 2 minutes
                var dueTime = ((forecastData != null) && forecastData.IsValid()) ?
                    new TimeSpan(4, 0, 0) : new TimeSpan(0, 2, 0);

                // create the timer
                _timer = new Timer(UpdateWeatherForecast, null, dueTime, new TimeSpan(4, 0, 0));

                // if we've gotten this far, we don't need to dispose ourselves
                dispose = false;
            }
            finally
            {
                if (dispose)
                {
                    Dispose();
                }
            }
        }
        public WeatherForecastData GetForecastData()
        {
            WeatherForecastData data;

            try
            {
                _forecastDataLock.AcquireReaderLock(100);
                data = (WeatherForecastData)_forecastData.Clone();
                _forecastDataLock.ReleaseReaderLock();
            }
            catch (ApplicationException)
            {
                data = new WeatherForecastData();
            }

            return data;
        }
        /// <summary>
        /// Parses the specified XML response from the National Weather Service (NWS)
        /// to the appropriate data fields.
        /// </summary>
        /// <param name="xml">The XML response from the NWS.</param>
        /// <returns></returns>
        public static WeatherForecastData Parse(string xml)
        {
            var data = new WeatherForecastData();

            try
            {
                var doc = new XmlDocument();
                doc.LoadXml(xml);

                data.Source = doc.SelectSingleNode("//title/text()").InnerText;

                data.ForecastDateTime = DateTime.Parse(doc.SelectSingleNode(
                    "/dwml/data/time-layout[1]/start-valid-time[1]/text()").InnerText);

                XmlNodeList tempNodes = doc.SelectNodes("/dwml/data/parameters/temperature[@type=\"apparent\"]/value");

                data.TemperatureHigh = Convert.ToInt32(doc.SelectSingleNode(
                    "/dwml/data/parameters/temperature[@type=\"apparent\"]/value[1]/text()").InnerText);

                if ((tempNodes != null) && (tempNodes.Count > 1))
                {
                    data.TemperatureLow = Convert.ToInt32(doc.SelectSingleNode(
                        "/dwml/data/parameters/temperature[@type=\"apparent\"]/value[2]/text()").InnerText);

                    // swap the high and low values if necessary
                    if (data.TemperatureLow > data.TemperatureHigh)
                    {
                        data.TemperatureLow ^= data.TemperatureHigh;
                        data.TemperatureHigh ^= data.TemperatureLow;
                        data.TemperatureLow ^= data.TemperatureHigh;
                    }
                }

                data.IconUri = doc.SelectSingleNode("//icon-link").InnerText;

                data.MoreInformationUri = doc.SelectSingleNode("//moreWeatherInformation/text()").InnerText;

                data.ProbabilityOfPrecipitation = Convert.ToInt32(doc.SelectSingleNode(
                    "/dwml/data/parameters/probability-of-precipitation/value/text()").InnerText);

                data.WindSpeedHigh = Convert.ToInt32(doc.SelectSingleNode(
                    "/dwml/data/parameters/wind-speed/value[1]/text()").InnerText);

                tempNodes = doc.SelectNodes("/dwml/data/parameters/wind-speed/value");

                if ((tempNodes != null) && (tempNodes.Count > 1))
                {
                    data.WindSpeedLow = Convert.ToInt32(doc.SelectSingleNode(
                        "/dwml/data/parameters/wind-speed/value[2]/text()").InnerText);
                }
            }
            catch (NullReferenceException exNull)
            {
                Debug.WriteLine(exNull.Message);
                data.Source = String.Empty;
            }
            catch (InvalidOperationException exOp)
            {
                Debug.WriteLine(exOp.Message);
                data.Source = String.Empty;
            }
            catch (XPathException exXPath)
            {
                Debug.WriteLine(exXPath.Message);
                data.Source = String.Empty;
            }

            return data;
        }