/// <summary>
        /// Refreshes the EPG.
        /// </summary>
        /// <param name="state">The state.</param>
        protected void RefreshEPG(object state)
        {
            EpgImportResult results = new EpgImportResult();

              Thread.CurrentThread.Name = Name;
              Thread.CurrentThread.Priority = ThreadPriority.Lowest;
              Thread.CurrentThread.IsBackground = true;
              Log.WriteFile("Starting EPG refresh job on thread #{0}", Thread.CurrentThread.ManagedThreadId);
              Log.WriteFile("UTC time is {0}, local time is {1} (DST is {2})", DateTime.Now.ToUniversalTime(), DateTime.Now.ToLocalTime(), DateTime.Now.IsDaylightSavingTime());
              Log.WriteFile("Rename Existing Channels is set to: {0}, with Template: {1}.", PluginSettings.RenameExistingChannels.ToString(), PluginSettings.ChannelNameTemplate);
              Log.WriteFile("Add New Digital Channels is set to: {0}, with External Input: {1}.", PluginSettings.AddNewChannels.ToString(), PluginSettings.ExternalInput.ToString());
              Log.WriteFile("Add New Digital Channels Country is set to: {0}.", PluginSettings.ExternalInputCountry.ToString());
              Log.WriteFile("Add New Analog Channels is set to: {0}, with External Input: {1}.", PluginSettings.AddAnalogChannels.ToString(), PluginSettings.ExternalInput.ToString());
              Log.WriteFile("Channel Sorting is set to: {0}.", PluginSettings.SortChannelsByNumber.ToString());
              Log.WriteFile("Allow Channel Matching without Frequency is set to: {0}.", PluginSettings.AllowChannelNumberOnlyMapping.ToString());
              Log.WriteFile("Delete Channels with No EPG Mapping is set to: {0}.", PluginSettings.DeleteChannelsWithNoEPGMapping.ToString());

              if (string.IsNullOrEmpty(PluginSettings.Username) || string.IsNullOrEmpty(PluginSettings.Password))
              {
            Log.WriteFile(MSG_BAD_USERNAME);
            Log.WriteFile("-Username: "******" -Password: "******"http://webservices.schedulesdirect.tmsdatadirect.com"))
              {
            Log.WriteFile("Not connected to Internet or unable to connect to the WebService at this time, skipping update");
            ScheduleNextRetrievalTime(false);
            NotifyPopUp(MSG_NO_INTERNET);
            return;
              }

              // Import initiator
              try
              {
            PrepareForEpgImport();
            results = RunEpgImport();
            FinalizeEpgImport(results);
              }
              catch (System.Net.WebException ex)
              {
            Log.WriteFile(ex.ToString());
            RollbackEpgImport();
            // Check to see if we failed because our login information was incorrect
            System.Net.HttpWebResponse httpWebResponse = ex.Response as System.Net.HttpWebResponse;
            if (httpWebResponse != null && httpWebResponse.StatusCode == System.Net.HttpStatusCode.Unauthorized)
            {
              Notify(MSG_ACCESS_DENIED);
            }
            else if (httpWebResponse != null && httpWebResponse.StatusCode == System.Net.HttpStatusCode.NotFound)
            {
              Notify(MSG_WEBSVC_NOCONN);
            }
              }
              catch (InvalidOperationException ex)
              {
            // This means the user has no lineup or their subscription is expired
            Log.WriteFile(ex.ToString());
            RollbackEpgImport();
            Notify(MSG_NO_LINEUP);
              }
              catch (Exception ex)
              {
            Log.WriteFile(ex.ToString());
            RollbackEpgImport();
              }
              finally
              {
            ScheduleNextRetrievalTime();
              }

              // Check if we managed to process any channels, if not, this probably means they didn't do an autotune
              if (results.ChannelsProcessed == 0)
              {
            Notify(MSG_NO_CHANNELS);
              }

              // Check for subscription expiration and notify user. You cannot renew until 7 days before expiration.
              // Once it has expired, we'll not be able to connect to download listings
              TimeSpan expires = (results.SubscriptionExpiration - DateTime.Now);
              if (expires.Days >= 0 && expires.Days < 7)
              {
            Notify(MSG_EXPIRES, expires.Days);
              }
              else if (PluginSettings.NotifyOnCompletion && results.ChannelsProcessed > 0 && GetLastProgramEntry() > DateTime.Now)
              {
            NotifyPopUp(MSG_EPG_UPDATED, GetLastProgramEntry());
              }
        }
        /// <summary>
        /// Runs the epg import.
        /// </summary>
        /// <returns>DateTime of the subscription expiration</returns>
        protected EpgImportResult RunEpgImport()
        {
            int UPDATE_HOURS = PluginSettings.LastMinuteGuideHours; // 24;
              SchedulesDirect.SoapEntities.DownloadResults listingData;
              EpgImportResult result = new EpgImportResult();
              DateTime lastDbEntry;
              DateTime startDate;
              DateTime endDate;
              string channelHash;
              int programCount;
              bool lineupHasChanged = false;

              // Get the end time of the latest program entry in the Epg before we start any imports
              lastDbEntry = GetLastProgramEntry();

              using (SchedulesDirect.SchedulesDirectWebService webService = new SchedulesDirect.SchedulesDirectWebService(PluginSettings.Username, PluginSettings.Password))
              {
            #region Update Epg with next 24 hours worth of data for last minute changes

            startDate = DateTime.Now;
            endDate = startDate.AddHours(UPDATE_HOURS);

            Log.WriteFile("Requesting {0} hours of program listings from [{1}] to [{2}]", UPDATE_HOURS, startDate, endDate);

            // The Using is sometimes covering the WebException, so handle it here
            try
            {
              listingData = webService.Download(startDate, endDate);
              result.SubscriptionExpiration = listingData.SubscriptionExpiration;
              result.Messages = listingData.Messages;
              result.UpdateRequired = false;
            }
            catch (System.Net.WebException ex)
            {
              CheckWebException(ex, true);

              return result;
            }
            catch (Exception ex)
            {
              Log.WriteFile("WARNING: Appears there is a problem with the WebService, {0} Inner: {1}", ex.ToString(), ex.InnerException.ToString());

              return result;
            }

            if (listingData.Data.Stations == null)
            {
              Log.WriteFile("WARNING: Skipping schedule import, appears there is a problem with the WebService");
              return result;
            }

            // Determine if there have been any changes in the SchedulesDirect channel lineup
            channelHash = GetStationsHash(listingData.Data.Stations);
            if (!PluginSettings.ChannelFingerprint.Equals(channelHash))
              lineupHasChanged = true;

            // Log any messages sent by SchedulesDirect to us
            foreach (string msg in listingData.Messages)
              Log.WriteFile("schedulesdirect msg: {0}", msg);

            // If debug mode is enabled, write the data to disk for analysis
            if (PluginSettings.DebugMode)
              WriteXTVD(listingData.Data, String.Format(@"{0}\MediaPortal TV Server\schedulesdirectupdate.xml", Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)));
            //WriteXTVD(listingData.Data, @"log\schedulesdirectupdate.xml");

            // Instantiate an EPG listing importer to do the actual import work
            using (Import.EpgListingsImporter epgListingsImporter = new Import.EpgListingsImporter(listingData))
            {
              epgListingsImporter.ChannelNameTemplate = PluginSettings.ChannelNameTemplate;
              epgListingsImporter.CreateChannels = PluginSettings.AddNewChannels;
              epgListingsImporter.RenameChannels = PluginSettings.RenameExistingChannels;
              epgListingsImporter.AllowChannelNumberOnlyMapping = PluginSettings.AllowChannelNumberOnlyMapping;
              epgListingsImporter.ExternalInputCountry = PluginSettings.ExternalInputCountry;
              epgListingsImporter.ExternalInput = PluginSettings.ExternalInput;
              epgListingsImporter.AllowChannelSorting = PluginSettings.SortChannelsByNumber;
              epgListingsImporter.CreateAnalogChannels = PluginSettings.AddAnalogChannels;
              epgListingsImporter.RemapChannelsOnLineupChange = PluginSettings.RemapChannelsOnLineupChange;
              epgListingsImporter.AddExtraInformationToShowDescription = PluginSettings.AddExtraDataToShowDescription;
              epgListingsImporter.LineupHasChanged = lineupHasChanged;
              epgListingsImporter.Delay = 10;

              result.ChannelsProcessed = epgListingsImporter.ImportChannels();

              Log.WriteFile("Sucessfully processed {0} of {1} channels", result.ChannelsProcessed, listingData.Data.Stations.List.Count);

              if (result.ChannelsProcessed == 0)
              {
            Log.WriteFile("WARNING: Skipping schedule import (no channels sucessfully processed)");
            return result;
              }
              else
              {
            programCount = epgListingsImporter.ImportPrograms();
            Log.WriteFile("Sucessfully Imported {0} of {1} schedule entries", programCount, listingData.Data.Schedules.List.Count);
              }
            }
            #endregion

            #region Full Epg update
            // Determine if there have been any changes in the Schedules Direct channel lineup
            //channelHash = GetStationsHash(listingData.Data.Stations);
            if (!lineupHasChanged)
            {
              Log.WriteFile("No changes detected in Schedules Direct channel lineup");
              // Set start of full grab to the end of the first grab or the midnight of the last epg program in db if later
              startDate = (lastDbEntry.Date > endDate ? lastDbEntry.Date : endDate);
            }
            else
            {
              Log.WriteFile("Detected a change in Schedules Direct channel lineup fingerprint, resetting start date");
              PluginSettings.ChannelFingerprint = channelHash;
              startDate = endDate; // Set the start date to the previous end update end date (e.g. 24 hours from now)
            }

            // Add user selected number of GuideDays to midnight today to get the desired end date
            endDate = DateTime.Now.Date.AddDays(PluginSettings.GuideDays);

            // endDate could have been set to < startDate if getting only 1 day of data
            if (endDate < startDate)
            {
              endDate = startDate.Date.AddDays((double)1);
            }

            // Check to see if we already have enough data in the EPG without asking for more. This could happen if
            // the user changes the config to ask for less days or if other import mechanisms have been used.
            if ((endDate < lastDbEntry) && !lineupHasChanged)
            {
              Log.WriteFile("Already have enough guide data, skipping data request");
              return result;
            }

            // Download program listings
            // The Using is sometimes covering the WebException, so handle it here
            try
            {
              Log.WriteFile("Requesting program listings from [{0}] to [{1}]", startDate, endDate);
              listingData = webService.Download(startDate, endDate);
            }
            catch (System.Net.WebException ex)
            {
              CheckWebException(ex, true);

              return result;
            }
            catch (Exception ex)
            {
              Log.WriteFile("WARNING: Appears there is a problem with the WebService, {0} Inner: {1}", ex.ToString(), ex.InnerException.ToString());

              return result;
            }

            // Log any messages sent by Schedules Direct
            foreach (string msg in listingData.Messages)
              Log.WriteFile("Schedules Direct message: {0}", msg);

            // If debug mode is enabled, write the data to disk for analysis
            if (PluginSettings.DebugMode)
              WriteXTVD(listingData.Data, String.Format(@"{0}\MediaPortal TV Server\SchedulesDirect.xml", Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)));
            //WriteXTVD(listingData.Data, @"log\SchedulesDirect.xml");

            using (Import.EpgListingsImporter epgListingsImporter = new Import.EpgListingsImporter(listingData))
            {
              epgListingsImporter.Delay = 10;
              epgListingsImporter.CreateChannels = PluginSettings.AddNewChannels;

              programCount = epgListingsImporter.ImportPrograms();
              Log.WriteFile("Imported {0} of {1} schedule entries", programCount, listingData.Data.Schedules.List.Count);
              Log.WriteFile("Last program entry in database was {0}, now {1}", lastDbEntry, GetLastProgramEntry());
            }
            #endregion

            result.StartOfUpdateRange = startDate;
            result.EndOfUpdateRange = endDate;
            result.UpdateRequired = true;
            return result;
              }
        }
        /// <summary>
        /// Finalizes the EPG import by
        ///     commiting the database transaction
        ///     removing overlapping programs
        ///     enabling event generation
        ///     notifyng the user of sucess or subscription expiration
        /// </summary>
        protected void FinalizeEpgImport(EpgImportResult results)
        {
            //Log.WriteFile("Removing any overlapping programs");
              //RemoveOverLappingPrograms();
              //TVDatabase.RemoveOverlappingPrograms();

              // Remove Channels with no EPG Mapping?
              if (PluginSettings.DeleteChannelsWithNoEPGMapping)
              {
            Log.WriteFile("Checking for channels with no EPG mapping to delete");
            int rnbr = RemoveChannelsWithNoEPGMapping();
            Log.WriteFile("Channels with no EPG Mapping Removed: {0}", rnbr);
              }

              //Log.WriteFile("Removing any Overlapping programs");
              //RemoveOverLappingPrograms();
              //Log.WriteFile("Check for Overlapping programs completed");

              // TODO: This does not work as expected, but is much faster
              //       Try to determine another way.
              //const int UPDATE_HOURS = 24;
              //Log.WriteFile("Checking for Overlapping programs in the next {0} hours of program listings", UPDATE_HOURS);
              //// Always check the next 24 hours for overlaps
              //RemoveOverLappingPrograms(DateTime.Now, DateTime.Now.AddHours(UPDATE_HOURS));

              //if (results.UpdateRequired)
              //{
              //   Log.WriteFile("Checking for overlapping programs from [{0}] to [{1}]", results.StartOfUpdateRange, results.EndOfUpdateRange);
              //   RemoveOverLappingPrograms(results.StartOfUpdateRange, results.EndOfUpdateRange);
              //}

              //Log.WriteFile("Committing the transaction");
              //TVDatabase.CommitTransaction();
              //TVDatabase.RemoveOverlappingPrograms();
              //TVDatabase.SupressEvents = false;
              // Reload the Television EPG
              //MediaPortal.GUI.TV.GUITVHome.Navigator.ReLoad();
        }