/// <summary>
        /// Finds the TV channel.
        /// </summary>
        /// <param name="station">The station.</param>
        /// <param name="map">The map.</param>
        /// <returns>TVChannel found or null</returns>
        protected Channel FindTVChannel(SchedulesDirect.SoapEntities.TVStation station, SchedulesDirect.SoapEntities.TVStationMap map, bool LineupIsLocalBroadcast)
        {
            string strTvChannel;
             int idTvChannel;

             /*
             Log.WriteFile("Attempting Channel Find/Match for: Callsign: {0}, Map Channel: {1}, Map Major {2}, Map Minor: {3}, Map StationID: {4} ",
            station.CallSign, map.Channel, map.ChannelMajor, map.ChannelMinor, map.StationId);
             */

             string xmlTvId = BuildXmlTvId(LineupIsLocalBroadcast, station, map);

             #region Check if the channel is already in EPG mapping database
             //Log.WriteFile("GetEPGMapping: {0} for {1}", station.Name, station.ID + XMLTVID);
             if (GetEPGMapping(xmlTvId, out idTvChannel, out strTvChannel))
             {
            Log.WriteFile("Channel {0} was found as {1} in EPG mapping database", station.Name, strTvChannel);
            FixDigitalTerrestrialChannelMap(idTvChannel, station, ref map);

            return Channel.Retrieve(idTvChannel);
             }
             #endregion

             #region Try locating the channel by callsign
             //Log.WriteFile("GetChannelByName: {0}", station.CallSign);
             if (GetChannelByName(station.CallSign, out idTvChannel) == true)
             {
            Channel mpChannel = Channel.Retrieve(idTvChannel);
            Log.WriteFile("Matched channel {0} to {1} using CallSign", station.CallSign, mpChannel.DisplayName);
            FixDigitalTerrestrialChannelMap(idTvChannel, station, ref map);

            return mpChannel;
             }
             #endregion

             #region Iterate through each channel looking for a match
             foreach (Channel mpChannel in _mpChannelCache)
             {

            // Get the TuningDetail for the Channel
            System.Collections.IList chDetailList = (System.Collections.IList)mpChannel.ReferringTuningDetail();
            if (chDetailList.Count <= 0)
               continue;
            TuningDetail chDetail = (TuningDetail)chDetailList[0];

            // Only look at non-mapped channels
            if (String.IsNullOrEmpty(mpChannel.ExternalId))
            {

               // Check for an ATSC major/minor channel number match
               ATSCChannel atscChannel = new ATSCChannel();
               if (LineupIsLocalBroadcast && GetATSCChannel(mpChannel, ref atscChannel))
               {
                  if (map.ChannelMajor == atscChannel.MajorChannel && map.ChannelMinor == atscChannel.MinorChannel)
                  {
                     Log.WriteFile("Matched channel {0} to {1} by ATSC channel ({2}-{3})",
                         station.CallSign, mpChannel.DisplayName, atscChannel.MajorChannel, atscChannel.MinorChannel);

                     return mpChannel;
                  }
               }
               // If the Lineup is a LocalBroadcast we want to give preference to
               // searching by Broadcast Number
               // else give preference to the Major Channel Number
               else if (LineupIsLocalBroadcast && !station.IsDigitalTerrestrial)
               {
                  // Not an ATSC channel so check for an over-the-air
                  //(by checking it has a frequency) broadcast channel number match
                  if (chDetail.ChannelNumber == station.BroadcastChannelNumber)
                  {
                     if (chDetail.Frequency != 0 || _allowChannelNumberMapping)
                     {
                        Log.WriteFile("Matched channel {0} to {1} by OTA broadcast channel ({2})",
                           station.CallSign, chDetail.Name, chDetail.ChannelNumber);

                        return mpChannel;
                     }
                  }
               }
               else if (!LineupIsLocalBroadcast)
               {
                  // Check for an over-the-air (by checking it has a frequency)
                  // major channel number match
                  if (chDetail.ChannelNumber == map.ChannelMajor)
                  {
                     if (chDetail.Frequency != 0 || _allowChannelNumberMapping)
                     {
                        Log.WriteFile("Matched channel {0} to {1} by lineup channel ({2})",
                           station.CallSign, chDetail.Name, chDetail.ChannelNumber);

                        return mpChannel;
                     }
                  }
               }
            }
             }
             #endregion

             #region Check the UnMapped Channels if the lineup has changed
             foreach (Channel mpChannel in _mpUnMappedChannelCache)
             {
            string[] xIds = mpChannel.ExternalId.Split(new char[] { '.' });
            string[] cIds = xmlTvId.Split(new char[] { '.' });

            if (xIds[1].ToLower() == cIds[1].ToLower())
            {
               _mpUnMappedChannelCache.Remove(mpChannel);
               return mpChannel;
            }
             }
             #endregion

             #region Iterate through each channel again looking @ MajorChannel Number even if it is a local broadcast
             foreach (Channel mpChannel in _mpChannelCache)
             {
            if (!String.IsNullOrEmpty(mpChannel.ExternalId))
               continue;

            System.Collections.IList chDetailList = (System.Collections.IList)mpChannel.ReferringTuningDetail();
            if (chDetailList.Count <= 0)
               continue;
            TuningDetail chDetail = (TuningDetail)chDetailList[0];

            if (LineupIsLocalBroadcast && !station.IsDigitalTerrestrial && chDetail.Frequency != 0)
            {
               // Check for an over-the-air (by checking it has a frequency) major channel number match
               if (chDetail.ChannelNumber == map.ChannelMajor)
               {
                  Log.WriteFile("Matched channel {0} to {1} by lineup channel ({2})",
                     station.CallSign, chDetail.Name, chDetail.ChannelNumber);

                  return mpChannel;
               }
            }
            else if (!LineupIsLocalBroadcast)
            {
               // Not an ATSC channel so check for an over-the-air (by checking it has a frequency) broadcast channel number match
               if (chDetail.ChannelNumber == station.BroadcastChannelNumber)
               {
                  Log.WriteFile("Matched channel {0} to {1} by OTA broadcast channel ({2})",
                     station.CallSign, chDetail.Name, chDetail.ChannelNumber);

                  return mpChannel;
               }
            }

             }
             #endregion

             return null;
        }
        /// <summary>
        /// Fills in the ATSCChannel detail from the provided TuningDetail.
        /// </summary>
        /// <param name="idTvChannel">The Channel Database ID</param>
        /// <param name="station">The station</param>
        /// <param name="map">The map</param>
        /// <returns>true if a fix was completed on the map.ChannelMajor and map.ChannelMinor else false</returns>
        protected bool FixDigitalTerrestrialChannelMap(int idTvChannel, SchedulesDirect.SoapEntities.TVStation station, ref SchedulesDirect.SoapEntities.TVStationMap map)
        {
            if (station.IsDigitalTerrestrial && map.ChannelMinor <= 0)
            {
               ATSCChannel atscEPGChannel = new ATSCChannel();
               Channel mpChannel = Channel.Retrieve(idTvChannel);
               if (GetATSCChannel(mpChannel, ref atscEPGChannel))
               {
              map.ChannelMajor = atscEPGChannel.MajorChannel;
              map.ChannelMinor = atscEPGChannel.MinorChannel;

              return true;
               }
            }

            return false;
        }
        /// <summary>
        /// Creates a XmlTvId string using the SchedulesDirect ID + (BroadcastChannel or ChannelString) and the XMLTVID constant
        /// </summary>
        /// <param name="IsLocalBroadcast">True if the TVStation is a Local Broadcast</param>
        /// <param name="tvStation">The TVStation</param>
        /// <param name="tvStationMap">The TVStationMap</param>
        /// <returns></returns>
        protected string BuildXmlTvId(bool IsLocalBroadcast, SchedulesDirect.SoapEntities.TVStation tvStation, SchedulesDirect.SoapEntities.TVStationMap tvStationMap)
        {
            string xmlId = tvStation.ID;
             if (IsLocalBroadcast && tvStation.BroadcastChannelNumber > 0 && !tvStation.IsDigitalTerrestrial)
            xmlId += "." + tvStation.BroadcastChannelNumber.ToString() + XMLTVID;
             else
            xmlId += "." + tvStationMap.ChannelString + XMLTVID;

             return xmlId;
        }
 /// <summary>
 /// Builds the name of the channel based on the template.
 /// </summary>
 /// <param name="tvStation">The tv station.</param>
 /// <param name="tvStationMap">The tv station map.</param>
 /// <returns></returns>
 protected string BuildChannelName(SchedulesDirect.SoapEntities.TVStation tvStation, SchedulesDirect.SoapEntities.TVStationMap tvStationMap)
 {
     string channelName = string.Empty;
      if (tvStation != null && tvStationMap != null)
      {
     channelName = this._channelNameTemplate.ToLower(System.Globalization.CultureInfo.CurrentCulture);
     channelName = channelName.Replace("{callsign}", tvStation.CallSign);
     channelName = channelName.Replace("{name}", tvStation.Name);
     channelName = channelName.Replace("{affiliate}", tvStation.Affiliate);
     channelName = channelName.Replace("{number}", tvStationMap.Channel);
     // debug
     //channelName = channelName + " BCNbr: " + tvStation.BroadcastChannelNumber.ToString();
     //channelName = channelName + " CMajr: " + tvStationMap.ChannelMajor.ToString();
      }
      return channelName;
 }
 /// <summary>
 /// Initializes a new instance of the <see cref="T:EpgListingsImporter"/> class.
 /// </summary>
 /// <param name="results">The results.</param>
 public EpgListingsImporter(SchedulesDirect.SoapEntities.DownloadResults results)
 {
     this._results = results;
 }
 /// <summary>
 /// Writes the Schedules Direct XML data structure to a file.
 /// </summary>
 /// <param name="listingData">The XTVD entity.</param>
 /// <param name="filename">The filename.</param>
 private static void WriteXTVD(SchedulesDirect.SoapEntities.XTVD listingData, string filename)
 {
     Log.WriteFile("Debug mode enabled - dumping data to SchedulesDirect.xml");
       System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings();
       settings.Indent = true;
       using (System.Xml.XmlWriter writer = System.Xml.XmlWriter.Create(filename, settings))
       {
     listingData.WriteTo(writer);
       }
 }
 /// <summary>
 /// Gets a MD5 hash for the collection of stations and returns as a Base64 string
 /// </summary>
 /// <param name="tvStations">The tv stations.</param>
 /// <returns>Base64 string</returns>
 protected static string GetStationsHash(SchedulesDirect.SoapEntities.TVStations tvStations)
 {
     // We create a string with all the ID values from the channels and return an base64 MD5 hash
       System.Security.Cryptography.MD5CryptoServiceProvider md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
       StringBuilder sb = new StringBuilder();
       tvStations.List.Sort(); // Sort the list so order changes will not effect the hash
       foreach (SchedulesDirect.SoapEntities.TVStation tvStation in tvStations.List)
       {
     sb.Append(tvStation.ID);
       }
       return Convert.ToBase64String(md5.ComputeHash(Encoding.Unicode.GetBytes(sb.ToString())));
 }