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

using System;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Web;
using CommunityServer.Blogs.Components;
using CommunityServer.Components;
using CommunityServer.Configuration;
using CookComputing.XmlRpc;

namespace CommunityServer.Blogs.Components
{
	/// <summary>
	/// Proxy class to cross-post to other blogs using the MetaWeblog API
	/// </summary>
	[Serializable]
	public class CrossPostingProxy : XmlRpcClientProtocol
	{

		#region Private Fields

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


		#endregion

		public CrossPostingProxy(int settingsID, WeblogPost wp, CSContext ctx, ObjectState state)
		{
			this.settingsID = settingsID;
			this.post = wp;
			this.context = ctx;
			this.postState = state;

			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;
			}

		}


		/// <summary>
		/// Cross-Posts the given post to other weblogs using the MetaWeblog API, as defined by the CrossPostingUrls property of the parent blog
		/// </summary>
		public static void CrossPost(WeblogPost wp, ObjectState state)
		{
			CSContext cntx = CSContext.Current;
			CrossPostingProxy proxy = new CrossPostingProxy(cntx.SettingsID, wp, cntx, state);
			ManagedThreadPool.QueueUserWorkItem(new WaitCallback(proxy.CrossPost));
		}


		#region MetaWeblog methods

		[XmlRpcMethod("metaWeblog.newPost", Description="Makes a new post to a designated blog using the MetaWeblog API. Returns postid as a string.")]
		public string newPost(string blogid, string username, string password, MetaWeblog.Post post, bool publish)
		{
			return (string) Invoke("newPost", new object[] {blogid, username, password, post, publish});
		}

		
		[XmlRpcMethod("metaWeblog.editPost",Description="Updates and existing post to a designated blog using the MetaWeblog API. Returns true if completed.")]
		public bool editPost(string postid, string username, string password, MetaWeblog.Post post, bool publish)
		{
			return (bool) Invoke("editPost", new object[] {postid, username, password, post, publish});
		}

		
		[XmlRpcMethod("metaWeblog.getPost", Description="Retrieves an existing post using the MetaWeblog API. Returns the MetaWeblog struct.")]
		public MetaWeblog.Post getPost(string postid, string username, string password)
		{
			return (MetaWeblog.Post) Invoke("getPost", new object[] {postid, username, password});
		}


		[XmlRpcMethod("blogger.deletePost", Description="Deletes a post.")]
		public bool deletePost(string appKey, string postid, string username, string password, bool publish)
		{
			return (bool) Invoke("deletePost", new object[] {appKey, postid, username, password, publish});
		}

		#endregion

		#region Private Helpers

		private bool CanCrossPost
		{
			get { return (weblog != null && post.EnableCrossPosting && weblog.CrossPostingSettings != null && weblog.CrossPostingSettings.Length > 0); }
		}

		private void CrossPost(object state)
		{

			// Create new Context
			CSContext cntx = CSContext.Create(settingsID);
			cntx.User = post.User;

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

			RemoteMetaBlogSettings[] settings = weblog.CrossPostingSettings;
			if ( settings == null)
				return;

			foreach (RemoteMetaBlogSettings setting in settings)
			{
				if (setting != null)
				{
					try
					{

						if (Globals.IsNullorEmpty(setting.Url))
							throw new Exception("The cross posting URL was null or empty!");

						this.Url = setting.Url;

						if (postState == ObjectState.Create)
						{
							// Create & populate MetaWeblog post struct
							MetaWeblog.Post MetaBlogPost = ConvertToNewPost(post, setting.Message);

							// Post to remote blog and save the remote PostID for use later if post is updated
							string remotePostID = newPost(setting.AppKey, setting.Username, setting.Password, MetaBlogPost, post.IsApproved);
							if (!Globals.IsNullorEmpty(remotePostID))
							{
								// We are only chaging the extended attribs and don't want to call all of the events again, so update using the DataProvider
								post.SetExtendedAttribute(setting.ID.ToString(), remotePostID);
								WeblogDataProvider wdp = WeblogDataProvider.Instance();
								wdp.UpdatePost(post, post.User.UserID);
							}

							// Log sucess message to the EventLog
							string message = String.Format("Cross-Posted Blog PostID {0} to {1}.", post.PostID, setting.Url);
							EventLogs.Info(message, "Weblogs", 305, this.settingsID);
						}
						else if (postState == ObjectState.Update)
						{
							// Retrieve the remote PostID for setting from the extended attribs
							string remotePostID = post.GetExtendedAttribute(setting.ID.ToString());
							if (!Globals.IsNullorEmpty(remotePostID))
							{
								// Retrieve the existing post
								MetaWeblog.Post MetaBlogPost = getPost(remotePostID, setting.Username, setting.Password);

								// Update it with new values & re-post
								UpdateExistingPost(MetaBlogPost, post, setting.Message);
								editPost(remotePostID, setting.Username, setting.Password, MetaBlogPost, post.IsApproved);

								// Log sucess message to the EventLog
								string message = String.Format("Updated the remote blog {0} for PostID {1}.", setting.Url, post.PostID);
								EventLogs.Info(message, "Weblogs", 305, this.settingsID);
							}
							else
							{
								// Log this failure to the EventLog
								string message = String.Format("Could not updated the remote blog {0} for PostID {1} because the remote PostID was not found in the extended attributes.", setting.Url, post.PostID);
								EventLogs.Warn(message, "Weblogs", 305, this.settingsID);
							}
						}
						else if (postState == ObjectState.Delete)
						{
							// Retrieve the remote PostID for setting from the extended attribs
							string remotePostID = post.GetExtendedAttribute(setting.ID.ToString());
							if (!Globals.IsNullorEmpty(remotePostID))
							{
								// Delete the remote post
								deletePost(setting.AppKey, remotePostID, setting.Username, setting.Password, true);

								// Log sucess message to the EventLog
								string message = String.Format("Deleted PostID {0} on the remote blog {1}.", post.PostID, setting.Url);
								EventLogs.Info(message, "Weblogs", 305, this.settingsID);
							}
							else
							{
								// Log this failure to the EventLog
								string message = String.Format("Could not delete PostID {0} on the remote blog {1} because the remote PostID was not found in the extended attributes.", post.PostID, setting.Url);
								EventLogs.Warn(message, "Weblogs", 305, this.settingsID);
							}
						}

					}
					catch (CookComputing.XmlRpc.XmlRpcException ex)
					{
						// Log this failure to the EventLog
						string message = String.Format("Cross-Post attempt to the url [{0}] failed for PostID {1}. Error message returned was: {2}.", setting.Url, post.PostID, ex.Message);
						EventLogs.Warn(message, "Weblogs", 305, this.settingsID);
					}
					catch (System.Exception ex)
					{
						// Log this failure to the EventLog
						string message = String.Format("Cross-Post attempt to the url [{0}] failed for PostID {1}. Error message returned was: {2}.", setting.Url, post.PostID, ex.Message);
						EventLogs.Warn(message, "Weblogs", 305, this.settingsID);
					}
				}
			}

		}

		private MetaWeblog.Post ConvertToNewPost(WeblogPost wp, string messageToAppend)
		{
			MetaWeblog.Post p = new MetaWeblog.Post();
			p.categories = wp.Categories;
			p.dateCreated = wp.UserTime;
			p.description = wp.Body;
			if (!Globals.IsNullorEmpty(messageToAppend))
				p.description += "\n" + messageToAppend;
			p.title = wp.Subject;
			return p;
		}

		private MetaWeblog.Post UpdateExistingPost(MetaWeblog.Post p, WeblogPost wp, string messageToAppend)
		{
			p.categories = wp.Categories;
			p.dateCreated = wp.UserTime;
			p.description = wp.Body;
			if (!Globals.IsNullorEmpty(messageToAppend))
				p.description += "\n" + messageToAppend;
			p.title = wp.Subject;
			return p;
		}


		#endregion

	}
}
