//------------------------------------------------------------------------------
// <copyright company="Telligent Systems">
//     Copyright (c) Telligent Systems Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Xml;
using CommunityServer.Components;
using CommunityServer.Configuration;
using CookComputing.XmlRpc;

namespace CommunityServer.Blogs.Components 
{

	[XmlRpcMissingMapping(MappingAction.Ignore)]
	public struct PingResult
	{
		public XmlRpcBoolean flerror;
		public string message;
	}
	
	/// <summary>
	/// Enables pinging of community blogging sites such as weblogs.com after the blog is updated.
	/// </summary>
	[Serializable]
	public class XmlRpcPings : XmlRpcClientProtocol
	{

		#region Private Fields

		private int settingsID;
		private WeblogPost post = null;
		private CSContext context = null;
		private Weblog weblog { get { return post.Weblog; } }

		#endregion

		public XmlRpcPings(int settingsID, WeblogPost wp, CSContext ctx)
		{
			this.settingsID = settingsID;
			this.post = wp;
			this.context = ctx;

			this.UserAgent = CSRequest.UserAgent;
			this.Timeout = 60000;

			// This improves compatibility with XML-RPC servers that do not fully comply with the XML-RPC specification.
			this.NonStandard = XmlRpcNonStandard.All;

			// Use a proxy server if one has been configured
			CSConfiguration config = CSConfiguration.GetConfig();
			if (config.ProxyHost != string.Empty)
			{
				WebProxy proxy = new WebProxy(config.ProxyHost, config.ProxyPort);

				if (config.ProxyBypassList != string.Empty)
					proxy.BypassList = config.ProxyBypassList.Split(',');
				proxy.BypassProxyOnLocal = config.ProxyBypassOnLocal;

				if (config.ProxyUsername != string.Empty)
					proxy.Credentials = new NetworkCredential(config.ProxyUsername, config.ProxyPassword);

				this.Proxy = proxy;
			}
		}

		public bool CanPing
		{
			get { return (weblog != null && weblog.EnablePings && weblog.PingUrls != null && weblog.PingUrls.Length > 0); }
		}

		/// <summary>
		/// Each ping has taken up to a minute to complete. This overload of SendPings enables
		/// SendPings to be executed on a seperate thread.
		/// </summary>
		/// <param name="useBackGroundThread">Should this operation happen on a new thread</param>
		public static void SendPings(WeblogPost wp, bool useBackGroundThread)
		{
			CSContext cntx = CSContext.Current;
			XmlRpcPings pinger = new XmlRpcPings(cntx.SettingsID, wp, cntx);
			if (useBackGroundThread)
			{
				ManagedThreadPool.QueueUserWorkItem(new WaitCallback(pinger.SendPings));
			}
			else
			{
				pinger.SendPings();
			}
		}

		public void SendPings(object state)
		{
			// Create new Context
			CSContext.Create(this.settingsID);

			SendPings();
		}

		
		public void SendPings()
		{

			//Can we proceed?
			if (!CanPing)
				return;

			// Gather information to pass to ping service(s)
			string name = weblog.Name;
			string url = context.HostPath + BlogUrls.Instance().HomePage(weblog.ApplicationKey);

			string feedUrl = "";
			if (weblog.EnableRss)
				feedUrl = context.HostPath + BlogUrls.Instance().Rss(weblog.ApplicationKey);
			else if (weblog.EnableAtom)
				feedUrl = context.HostPath + BlogUrls.Instance().Atom(weblog.ApplicationKey);


			// Not supporting RssPing for now until we get more info about spec.
			/*
			string postTitle = post.Subject;
			string postUrl = BlogUrls.Instance().Post(post);
			string postFeedUrl = BlogUrls.Instance().RssPost(weblog.ApplicationKey, post.PostID);

						string postCategories = "";
			if (post.HasCategories)
				postCategories = post.Categories.GetValue(0) as string;
			*/

			// Use site setting unless indiv blog has overriden the setting
			string[] pingUrls = WeblogConfiguration.Instance().DefaultPingServices;
			if (weblog.PingUrls != null && weblog.PingUrls.Length > 0)
				pingUrls = weblog.PingUrls;

			if (pingUrls == null || pingUrls.Length == 0)
				return;

			foreach (string pingUrl in pingUrls)
			{
				if (!Globals.IsNullorEmpty(pingUrl))
				{
					this.Url = pingUrl;
					bool result = false;
					string errorMessage = "";

					PingType requestedPingType = PingType.Auto;
					PingType actualPingType = PingType.Auto;
					object cachedPingType = CSCache.Get(CacheKey(pingUrl));
					if (cachedPingType != null)
						requestedPingType = (PingType)cachedPingType;
					
					// Not supporting RssPing for now until we get more info about spec
					/*
					if (requestedPingType == PingType.Auto || requestedPingType == PingType.RssPing)
					{
						try
						{
							PingResult response = RssPing(name, url, feedUrl, postTitle, postUrl, postCategories, postFeedUrl);
							if (response.flerror == null || response.flerror == false)
							{
								result = true;
								actualPingType = PingType.RssPing;
							}
						}
						catch (CookComputing.XmlRpc.XmlRpcException ex) 
						{
							errorMessage = ex.Message;
						} 
						catch (System.Exception ex) 
						{
							errorMessage = ex.Message;
						} 
					}
					*/

					if ((requestedPingType == PingType.Auto && !result) || requestedPingType == PingType.ExtendedPing)
					{
						try
						{
							PingResult response = ExtendedPing(name, url, feedUrl);
							if (response.flerror != null && response.flerror == true)
							{
								if (Globals.IsNullorEmpty(response.message))
									errorMessage = "Remote weblogUpdates.extendedPing service indicated an error but provided no error message.";
								else
									errorMessage = response.message;
							}
							else
							{
								result = true;
								actualPingType = PingType.ExtendedPing;
							}
						}
						catch (CookComputing.XmlRpc.XmlRpcException ex) 
						{
							errorMessage = ex.Message;
						} 
						catch (System.Exception ex) 
						{
							errorMessage = ex.Message;
						} 
					}

					if ((requestedPingType == PingType.Auto && !result) || requestedPingType == PingType.BasicPing)
					{
						try
						{
							PingResult response = BasicPing(name, url);
							if (response.flerror != null && response.flerror == true)
							{
								if (Globals.IsNullorEmpty(response.message))
									errorMessage = "Remote weblogUpdates.ping service indicated an error but provided no error message.";
								else
									errorMessage = response.message;
							}
							else
							{
								result = true;
								actualPingType = PingType.BasicPing;
							}
						}
						catch (CookComputing.XmlRpc.XmlRpcException ex) 
						{
							errorMessage = ex.Message;
						} 
						catch (System.Exception ex) 
						{
							errorMessage = ex.Message;
						} 
					}
			
					// If the requested ping type was Auto, cache the actual ping type used for this url for next time
					if (requestedPingType == PingType.Auto)
						CSCache.Insert(CacheKey(pingUrl), actualPingType, 7 * CSCache.DayFactor);

					// Log succcess or failure to EventLog
					if (result)
					{
						string message = String.Format("Weblog Ping sent to {0} for PostID {1}.", pingUrl, post.PostID);
						EventLogs.Info(message, "Weblogs", 303, CSContext.Current.SettingsID);
					}
					else
					{
						string message = String.Format("Weblog Ping attempt to the url [{0}] failed for PostID {1}. Error message returned was: {2}.", pingUrl, post.PostID, errorMessage);
						EventLogs.Warn(message, "Weblogs", 303, CSContext.Current.SettingsID);
					}

				}
			}

		}

		private string CacheKey(string url)
		{
			return string.Format("PingService:PingType:{0}", url);
		}


		[XmlRpcMethod("weblogUpdates.ping")] 
		public PingResult BasicPing(string name, string url)
		{
			return (PingResult)Invoke("BasicPing", new Object[]{ name, url }); 
		}

		[XmlRpcMethod("weblogUpdates.extendedPing")] 
		public PingResult ExtendedPing(string name, string url, string feedUrl)
		{
			return (PingResult)Invoke("ExtendedPing", new Object[]{ name, url, url, feedUrl }); 
		}
	
		[XmlRpcMethod("weblogUpdates.rssPing")] 
		public PingResult RssPing(string name, string url, string feedUrl, string postTitle, string postUrl, string postCategories, string postFeedUrl)
		{
			return (PingResult)Invoke("RssPing", new Object[]{ name, url, feedUrl, postTitle, postUrl, postCategories, postFeedUrl }); 
		}

	

	}
}
