/*--------------------------------------------------------------------------
* URL短縮支援ライブラリ
* version 1.0.5 (Dec. 29th, 2010)
*
* This code is written by aqua877 http://aqua-s.ddo.jp/
*--------------------------------------------------------------------------*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.ComponentModel;
using System.Xml.Linq;
using System.Text.RegularExpressions;
using System.Reflection;
using System.Collections.Specialized;

namespace Aqua877.WinApp.Lib
{
	/// <summary>
	/// ShortenURLFinishedイベントのデータを提供します。
	/// </summary>
	public class ShortenURLFinishedEventArgs : EventArgs
	{
		public Uri URLBeforeShortening
		{
			get;
			private set;
		}

		public Uri URLAfterShortening
		{
			get;
			private set;
		}

		public Exception Error
		{
			get;
			private set;
		}

		public ShortenURLFinishedEventArgs(Uri urlBeforeShortening, Uri urlAfterShortening, Exception error)
			: base()
		{
			this.URLBeforeShortening = urlBeforeShortening;
			this.URLAfterShortening = urlAfterShortening;
			this.Error = error;
		}
	}

	/// <summary>
	/// ExpandURLFinishedイベントのデータを提供します。
	/// </summary>
	public class ExpandURLFinishedEventArgs : EventArgs
	{
		public Uri URLBeforeExpanding
		{
			get;
			private set;
		}

		public Uri URLAfterExpanding
		{
			get;
			private set;
		}

		public Exception Error
		{
			get;
			private set;
		}

		public ExpandURLFinishedEventArgs(Uri urlBeforeExpanding, Uri urlAfterExpanding, Exception error)
			: base()
		{
			this.URLBeforeExpanding = urlBeforeExpanding;
			this.URLAfterExpanding = urlAfterExpanding;
			this.Error = error;
		}
	}

	/// <summary>
	/// IURLShortenのShortenURLFinishedイベントを処理するメソッドを表します。
	/// </summary>
	/// <param name="sender"></param>
	/// <param name="e"></param>
	public delegate void ShortenURLFinishedEventHandler(object sender, ShortenURLFinishedEventArgs e);

	/// <summary>
	/// IURLShortenのExpandURLFinishedイベントを処理するメソッドを表します。
	/// </summary>
	/// <param name="sender"></param>
	/// <param name="e"></param>
	public delegate void ExpandURLFinishedEventHandler(object sender, ExpandURLFinishedEventArgs e);

	/// <summary>
	/// URLの短縮サービスを利用するための共通のメソッドを定義します。
	/// </summary>
	public interface IURLShorten : IDisposable
	{
		/// <summary>
		/// 指定したURLを短縮します。
		/// </summary>
		/// <param name="url"></param>
		/// <returns></returns>
		Uri ShortenURL(Uri url);

		/// <summary>
		/// 指定した短縮URLを復元します。
		/// </summary>
		/// <param name="url"></param>
		/// <returns></returns>
		Uri ExpandURL(Uri url);

		/// <summary>
		/// 指定したURLを短縮します。このメソッドは、呼び出し元のスレッドをブロックしません。
		/// </summary>
		/// <param name="url"></param>
		void BeginShortenURL(Uri url);

		/// <summary>
		/// 保留中の非同期URL短縮操作を終了します。
		/// </summary>
		void EndShortenURL();

		/// <summary>
		/// 指定した短縮URLを復元します。このメソッドは、呼び出し元のスレッドをブロックしません。
		/// </summary>
		/// <param name="url"></param>
		void BeginExpandURL(Uri url);

		/// <summary>
		/// 保留中の非同期短縮URL復元操作を終了します。
		/// </summary>
		void EndExpandURL();
		
		/// <summary>
		/// 非同期のURL短縮操作の完了時に発生します。
		/// </summary>
		event ShortenURLFinishedEventHandler ShortenURLFinished;

		/// <summary>
		/// 非同期の短縮URL復元操作完了時に発生します。
		/// </summary>
		event ExpandURLFinishedEventHandler ExpandURLFinished;
	}

	/// <summary>
	/// bit.lyでのURL短縮用のメソッドを提供します。
	/// </summary>
	public sealed class BitLyURLShortener : IURLShorten
	{
		private WebClient ShortenServiceConnector;
		private WebClient ExpandServiceConnector;

		private string User;
		private string ApiKey;

		public BitLyURLShortener(string user, string apiKey)
		{
			if (user == null)
			{
				throw new ArgumentNullException("user");
			}

			if (apiKey == null)
			{
				throw new ArgumentNullException("apiKey");
			}

			if (user == string.Empty)
			{
				throw new ArgumentException("ユーザー名を空にすることは出来ません。", "user");
			}

			if (apiKey == string.Empty)
			{
				throw new ArgumentException("APIキーを空にすることは出来ません。", "apiKey");
			}

			this.User = user;
			this.ApiKey = apiKey;
		}

		private void OnShortenURLFinished(ShortenURLFinishedEventArgs e)
		{
			if (this.ShortenURLFinished != null)
			{
				this.ShortenURLFinished(this, e);
			}
		}

		private void OnExpandURLFinished(ExpandURLFinishedEventArgs e)
		{
			if (this.ExpandURLFinished != null)
			{
				this.ExpandURLFinished(this, e);
			}
		}

		public Uri ShortenURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			this.ShortenServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = String.Format("http://api.bit.ly/shorten?version=2.0.1&format=xml&login={0}&apiKey={1}&longUrl={2}", this.User, this.ApiKey, url);

			string result = this.ShortenServiceConnector.DownloadString(api);

			XDocument document = XDocument.Parse(result);

			string errorCode = document.Descendants("errorCode").Single().Value;
			string errorMessgage = document.Descendants("errorMessage").Single().Value;

			if (errorCode != "0" && errorMessgage != "")
			{
				throw new WebException(String.Format("bit.ly APIがエラーを返しました。エラーコード : {0} エラーメッセージ : {1}", errorCode, errorMessgage));
			}

			string shortenURL = document.Descendants("shortUrl").Single().Value;
			return new Uri(shortenURL);
		}

		public Uri ExpandURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			if (url.Host != "bit.ly")
			{
				throw new ArgumentException("bit.lyによって短縮されたURLではありません。", "url");
			}

			this.ExpandServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = String.Format("{0}+", url);

			string result = this.ShortenServiceConnector.DownloadString(api);

			Regex pattern = new Regex(String.Format("<dd><a href=\"(.*?)\" onclick=\"javascript:document.location.href='{0}';\">", url));
			Match match = pattern.Match(result);

			if (match.Success)
			{
				return new Uri(match.Groups[1].Value);
			}
			else
			{
				return null;
			}
		}

		public void BeginShortenURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			this.ShortenServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = String.Format("http://api.bit.ly/shorten?version=2.0.1&format=xml&login={0}&apiKey={1}&longUrl={2}", this.User, this.ApiKey, url);

			this.ShortenServiceConnector.DownloadStringCompleted +=
				(sender, e) =>
				{
					if (e.Error != null)
					{
						this.OnShortenURLFinished(new ShortenURLFinishedEventArgs(url, null, e.Error));
					}
					else
					{
						XDocument document = XDocument.Parse(e.Result);

						string errorCode = document.Descendants("errorCode").Single().Value;
						string errorMessgage = document.Descendants("errorMessage").Single().Value;

						if (errorCode != "0" && errorMessgage != "")
						{
							Exception error = new WebException(String.Format("bit.ly APIがエラーを返しました。エラーコード : {0} エラーメッセージ : {1}", errorCode, errorMessgage));
							this.OnShortenURLFinished(new ShortenURLFinishedEventArgs(url, null, error));
						}
						else
						{
							string shortenURL = document.Descendants("shortUrl").Single().Value;

							this.OnShortenURLFinished(new ShortenURLFinishedEventArgs(url, new Uri(shortenURL), null));
						}
					}
				};

			this.ShortenServiceConnector.DownloadStringAsync(new Uri(api));
		}

		public void EndShortenURL()
		{
			this.ShortenServiceConnector.CancelAsync();
		}

		public void BeginExpandURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			if (url.Host != "bit.ly")
			{
				throw new ArgumentException("bit.lyによって短縮されたURLではありません。", "url");
			}

			this.ExpandServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = String.Format("{0}+", url);

			this.ExpandServiceConnector.DownloadStringCompleted +=
				(sender, e) =>
				{
					if (e.Error != null)
					{
						this.OnExpandURLFinished(new ExpandURLFinishedEventArgs(url, null, e.Error));
					}
					else
					{
						Regex pattern = new Regex(String.Format("<dd><a href=\"(.*?)\" onclick=\"javascript:document.location.href='{0}';\">", url));
						Match match = pattern.Match(e.Result);

						if (match.Success)
						{
							this.OnExpandURLFinished(new ExpandURLFinishedEventArgs(url, new Uri(match.Groups[1].Value), null));
						}
						else
						{
							this.OnExpandURLFinished(new ExpandURLFinishedEventArgs(url, null, null));
						}
					}
				};

			this.ExpandServiceConnector.DownloadStringAsync(new Uri(api));
		}

		public void EndExpandURL()
		{
			this.ExpandServiceConnector.CancelAsync();
		}

		public void Dispose()
		{
			this.ShortenServiceConnector.Dispose();
			this.ExpandServiceConnector.Dispose();
		}

		public event ShortenURLFinishedEventHandler ShortenURLFinished;

		public event ExpandURLFinishedEventHandler ExpandURLFinished;
	}

	/// <summary>
	/// ux.nuでのURL短縮用のメソッドを提供します。
	/// </summary>
	public sealed class UxNuURLShortener : IURLShorten
	{
		private WebClient ShortenServiceConnector;
		private WebClient ExpandServiceConnector;

		public UxNuURLShortener()
		{
		}

		private void OnShortenURLFinished(ShortenURLFinishedEventArgs e)
		{
			if (this.ShortenURLFinished != null)
			{
				this.ShortenURLFinished(this, e);
			}
		}

		private void OnExpandURLFinished(ExpandURLFinishedEventArgs e)
		{
			if (this.ExpandURLFinished != null)
			{
				this.ExpandURLFinished(this, e);
			}
		}

		public Uri ShortenURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			this.ShortenServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = String.Format("http://ux.nu/api/short?url={0}&format=plain", url);

			string result = this.ShortenServiceConnector.DownloadString(api);

			return new Uri(result);
		}

		public Uri ExpandURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			if (url.Host != "ux.nu")
			{
				throw new ArgumentException("ux.nuによって短縮されたURLではありません。", "url");
			}

			this.ExpandServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = String.Format("http://ux.nu/hugeurl?url={0}", url);

			string result = this.ExpandServiceConnector.DownloadString(api);

			return new Uri(result);
		}

		public void BeginShortenURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			this.ShortenServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = String.Format("http://ux.nu/api/short?url={0}&format=plain", url);

			this.ShortenServiceConnector.DownloadStringCompleted +=
				(sender, e) =>
				{
					if (e.Error != null)
					{
						this.OnShortenURLFinished(new ShortenURLFinishedEventArgs(url, null, e.Error));
					}
					else
					{
						this.OnShortenURLFinished(new ShortenURLFinishedEventArgs(url, new Uri(e.Result), null));
					}
				};

			this.ShortenServiceConnector.DownloadStringAsync(new Uri(api));
		}

		public void EndShortenURL()
		{
			this.ShortenServiceConnector.CancelAsync();
		}

		public void BeginExpandURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			this.ExpandServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			if (url.Host != "ux.nu")
			{
				throw new ArgumentException("ux.nuによって短縮されたURLではありません。", "url");
			}

			string api = String.Format("http://ux.nu/hugeurl?url={0}", url);

			this.ExpandServiceConnector.DownloadStringCompleted +=
				(sender, e) =>
				{
					if (e.Error != null)
					{
						this.OnExpandURLFinished(new ExpandURLFinishedEventArgs(url, null, e.Error));
					}
					else
					{
						this.OnExpandURLFinished(new ExpandURLFinishedEventArgs(url, new Uri(e.Result), null));
					}
				};

			this.ExpandServiceConnector.DownloadStringAsync(new Uri(api));
		}

		public void EndExpandURL()
		{
			this.ExpandServiceConnector.CancelAsync();
		}

		public void Dispose()
		{
			this.ShortenServiceConnector.Dispose();
			this.ExpandServiceConnector.Dispose();
		}

		public event ShortenURLFinishedEventHandler ShortenURLFinished;

		public event ExpandURLFinishedEventHandler ExpandURLFinished;
	}

	/// <summary>
	/// j.mpでのURL短縮用のメソッドを提供します。
	/// </summary>
	public sealed class JMpURLShortener : IURLShorten
	{
		private WebClient ShortenServiceConnector;
		private WebClient ExpandServiceConnector;

		private string User;
		private string ApiKey;

		public JMpURLShortener(string user, string apiKey)
		{
			if (user == null)
			{
				throw new ArgumentNullException("user");
			}

			if (apiKey == null)
			{
				throw new ArgumentNullException("apiKey");
			}

			if (user == string.Empty)
			{
				throw new ArgumentException("ユーザー名を空にすることは出来ません。", "user");
			}

			if (apiKey == string.Empty)
			{
				throw new ArgumentException("APIキーを空にすることは出来ません。", "apiKey");
			}

			this.User = user;
			this.ApiKey = apiKey;
		}

		private void OnShortenURLFinished(ShortenURLFinishedEventArgs e)
		{
			if (this.ShortenURLFinished != null)
			{
				this.ShortenURLFinished(this, e);
			}
		}

		private void OnExpandURLFinished(ExpandURLFinishedEventArgs e)
		{
			if (this.ExpandURLFinished != null)
			{
				this.ExpandURLFinished(this, e);
			}
		}

		public Uri ShortenURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			this.ShortenServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = String.Format("http://api.j.mp/shorten?version=2.0.1&format=xml&login={0}&apiKey={1}&longUrl={2}", this.User, this.ApiKey, url);

			string result = this.ShortenServiceConnector.DownloadString(api);

			XDocument document = XDocument.Parse(result);

			string errorCode = document.Descendants("errorCode").Single().Value;
			string errorMessgage = document.Descendants("errorMessage").Single().Value;

			if (errorCode != "0" && errorMessgage != "")
			{
				throw new WebException(String.Format("j.mp APIがエラーを返しました。エラーコード : {0} エラーメッセージ : {1}", errorCode, errorMessgage));
			}

			string shortenURL = document.Descendants("shortUrl").Single().Value;
			return new Uri(shortenURL);
		}

		public Uri ExpandURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			if (url.Host != "j.mp")
			{
				throw new ArgumentException("j.mpによって短縮されたURLではありません。", "url");
			}

			this.ExpandServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = String.Format("{0}+", url);

			string result = this.ShortenServiceConnector.DownloadString(api);

			Regex pattern = new Regex(String.Format("<dd><a href=\"(.*?)\" onclick=\"javascript:document.location.href='{0}';\">", url));
			Match match = pattern.Match(result);

			if (match.Success)
			{
				return new Uri(match.Groups[1].Value);
			}
			else
			{
				return null;
			}
		}

		public void BeginShortenURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			this.ShortenServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = String.Format("http://api.j.mp/shorten?version=2.0.1&format=xml&login={0}&apiKey={1}&longUrl={2}", this.User, this.ApiKey, url);

			this.ShortenServiceConnector.DownloadStringCompleted +=
				(sender, e) =>
				{
					if (e.Error != null)
					{
						this.OnShortenURLFinished(new ShortenURLFinishedEventArgs(url, null, e.Error));
					}
					else
					{
						XDocument document = XDocument.Parse(e.Result);

						string errorCode = document.Descendants("errorCode").Single().Value;
						string errorMessgage = document.Descendants("errorMessage").Single().Value;

						if (errorCode != "0" && errorMessgage != "")
						{
							Exception error = new WebException(String.Format("j.mp APIがエラーを返しました。エラーコード : {0} エラーメッセージ : {1}", errorCode, errorMessgage));
							this.OnShortenURLFinished(new ShortenURLFinishedEventArgs(url, null, error));
						}
						else
						{
							string shortenURL = document.Descendants("shortUrl").Single().Value;

							this.OnShortenURLFinished(new ShortenURLFinishedEventArgs(url, new Uri(shortenURL), null));
						}
					}
				};

			this.ShortenServiceConnector.DownloadStringAsync(new Uri(api));
		}

		public void EndShortenURL()
		{
			this.ShortenServiceConnector.CancelAsync();
		}

		public void BeginExpandURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			if (url.Host != "bit.ly")
			{
				throw new ArgumentException("j.mpによって短縮されたURLではありません。", "url");
			}

			this.ExpandServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = String.Format("{0}+", url);

			this.ExpandServiceConnector.DownloadStringCompleted +=
				(sender, e) =>
				{
					if (e.Error != null)
					{
						this.OnExpandURLFinished(new ExpandURLFinishedEventArgs(url, null, e.Error));
					}
					else
					{
						Regex pattern = new Regex(String.Format("<dd><a href=\"(.*?)\" onclick=\"javascript:document.location.href='{0}';\">", url));
						Match match = pattern.Match(e.Result);

						if (match.Success)
						{
							this.OnExpandURLFinished(new ExpandURLFinishedEventArgs(url, new Uri(match.Groups[1].Value), null));
						}
						else
						{
							this.OnExpandURLFinished(new ExpandURLFinishedEventArgs(url, null, null));
						}
					}
				};

			this.ExpandServiceConnector.DownloadStringAsync(new Uri(api));
		}

		public void EndExpandURL()
		{
			this.ExpandServiceConnector.CancelAsync();
		}

		public void Dispose()
		{
			this.ShortenServiceConnector.Dispose();
			this.ExpandServiceConnector.Dispose();
		}

		public event ShortenURLFinishedEventHandler ShortenURLFinished;

		public event ExpandURLFinishedEventHandler ExpandURLFinished;
	}

	/// <summary>
	/// is.gdでのURL短縮用のメソッドを提供します。
	/// </summary>
	public sealed class IsGdURLShortener : IURLShorten
	{
		private WebClient ShortenServiceConnector;
		private WebClient ExpandServiceConnector;

		public IsGdURLShortener()
		{
		}

		private void OnShortenURLFinished(ShortenURLFinishedEventArgs e)
		{
			if (this.ShortenURLFinished != null)
			{
				this.ShortenURLFinished(this, e);
			}
		}

		private void OnExpandURLFinished(ExpandURLFinishedEventArgs e)
		{
			if (this.ExpandURLFinished != null)
			{
				this.ExpandURLFinished(this, e);
			}
		}

		public Uri ShortenURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			this.ShortenServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = String.Format("http://is.gd/api.php?longurl={0}", url);

			string result = this.ShortenServiceConnector.DownloadString(api);

			return new Uri(result);
		}

		public Uri ExpandURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			if (url.Host != "is.gd")
			{
				throw new ArgumentException("is.gdによって短縮されたURLではありません。", "url");
			}

			this.ExpandServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			this.ExpandServiceConnector.DownloadString(url);

			var info = typeof(WebClient).GetField("m_WebResponse", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
			var response = info.GetValue(this.ExpandServiceConnector) as HttpWebResponse;

			return response.ResponseUri;
		}

		public void BeginShortenURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			this.ShortenServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = String.Format("http://is.gd/api.php?longurl={0}", url);

			this.ShortenServiceConnector.DownloadStringCompleted +=
				(sender, e) =>
				{
					if (e.Error != null)
					{
						this.OnShortenURLFinished(new ShortenURLFinishedEventArgs(url, null, e.Error));
					}
					else
					{
						this.OnShortenURLFinished(new ShortenURLFinishedEventArgs(url, new Uri(e.Result), null));
					}
				};

			this.ShortenServiceConnector.DownloadStringAsync(new Uri(api));
		}

		public void EndShortenURL()
		{
			this.ShortenServiceConnector.CancelAsync();
		}

		public void BeginExpandURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			if (url.Host != "is.gd")
			{
				throw new ArgumentException("is.gdによって短縮されたURLではありません。", "url");
			}

			this.ExpandServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = String.Format("http://ux.nu/hugeurl?url={0}", url);

			this.ExpandServiceConnector.DownloadStringCompleted +=
				(sender, e) =>
				{
					if (e.Error != null)
					{
						this.OnExpandURLFinished(new ExpandURLFinishedEventArgs(url, null, e.Error));
					}
					else
					{
						var info = typeof(WebClient).GetField("m_WebResponse", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
						var response = info.GetValue(this.ExpandServiceConnector) as HttpWebResponse;

						this.OnExpandURLFinished(new ExpandURLFinishedEventArgs(url, response.ResponseUri, null));
					}
				};

			this.ExpandServiceConnector.DownloadStringAsync(new Uri(api));
		}

		public void EndExpandURL()
		{
			this.ExpandServiceConnector.CancelAsync();
		}

		public void Dispose()
		{
			this.ShortenServiceConnector.Dispose();
			this.ExpandServiceConnector.Dispose();
		}

		public event ShortenURLFinishedEventHandler ShortenURLFinished;

		public event ExpandURLFinishedEventHandler ExpandURLFinished;
	}

	/// <summary>
	/// tinyurl.comでのURL短縮用のメソッドを提供します。
	/// </summary>
	public sealed class TinyurlComURLShortener : IURLShorten
	{
		private WebClient ShortenServiceConnector;
		private WebClient ExpandServiceConnector;

		public TinyurlComURLShortener()
		{
		}

		private void OnShortenURLFinished(ShortenURLFinishedEventArgs e)
		{
			if (this.ShortenURLFinished != null)
			{
				this.ShortenURLFinished(this, e);
			}
		}

		private void OnExpandURLFinished(ExpandURLFinishedEventArgs e)
		{
			if (this.ExpandURLFinished != null)
			{
				this.ExpandURLFinished(this, e);
			}
		}

		public Uri ShortenURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			this.ShortenServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = String.Format("http://tinyurl.com/api-create.php?url={0}", url);

			string result = this.ShortenServiceConnector.DownloadString(api);

			return new Uri(result);
		}

		public Uri ExpandURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			if (url.Host != "tinyurl.com")
			{
				throw new ArgumentException("tinyurl.comによって短縮されたURLではありません。", "url");
			}

			this.ExpandServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			this.ExpandServiceConnector.DownloadString(url);

			var info = typeof(WebClient).GetField("m_WebResponse", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
			var response = info.GetValue(this.ExpandServiceConnector) as HttpWebResponse;

			return response.ResponseUri;
		}

		public void BeginShortenURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			this.ShortenServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = String.Format("http://tinyurl.com/api-create.php?url={0}", url);

			this.ShortenServiceConnector.DownloadStringCompleted +=
				(sender, e) =>
				{
					if (e.Error != null)
					{
						this.OnShortenURLFinished(new ShortenURLFinishedEventArgs(url, null, e.Error));
					}
					else
					{
						this.OnShortenURLFinished(new ShortenURLFinishedEventArgs(url, new Uri(e.Result), null));
					}
				};

			this.ShortenServiceConnector.DownloadStringAsync(new Uri(api));
		}

		public void EndShortenURL()
		{
			this.ShortenServiceConnector.CancelAsync();
		}

		public void BeginExpandURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			if (url.Host != "tinyurl.com")
			{
				throw new ArgumentException("tinyurl.comによって短縮されたURLではありません。", "url");
			}

			this.ExpandServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = String.Format("http://ux.nu/hugeurl?url={0}", url);

			this.ExpandServiceConnector.DownloadStringCompleted +=
				(sender, e) =>
				{
					if (e.Error != null)
					{
						this.OnExpandURLFinished(new ExpandURLFinishedEventArgs(url, null, e.Error));
					}
					else
					{
						var info = typeof(WebClient).GetField("m_WebResponse", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
						var response = info.GetValue(this.ExpandServiceConnector) as HttpWebResponse;

						this.OnExpandURLFinished(new ExpandURLFinishedEventArgs(url, response.ResponseUri, null));
					}
				};

			this.ExpandServiceConnector.DownloadStringAsync(new Uri(api));
		}

		public void EndExpandURL()
		{
			this.ExpandServiceConnector.CancelAsync();
		}

		public void Dispose()
		{
			this.ShortenServiceConnector.Dispose();
			this.ExpandServiceConnector.Dispose();
		}

		public event ShortenURLFinishedEventHandler ShortenURLFinished;

		public event ExpandURLFinishedEventHandler ExpandURLFinished;
	}

	/// <summary>
	/// twurl.nlでのURL短縮用のメソッドを提供します。
	/// </summary>
	public sealed class TwurlNlURLShortener : IURLShorten
	{
		private WebClient ShortenServiceConnector;
		private WebClient ExpandServiceConnector;

		public TwurlNlURLShortener()
		{
		}

		private void OnShortenURLFinished(ShortenURLFinishedEventArgs e)
		{
			if (this.ShortenURLFinished != null)
			{
				this.ShortenURLFinished(this, e);
			}
		}

		private void OnExpandURLFinished(ExpandURLFinishedEventArgs e)
		{
			if (this.ExpandURLFinished != null)
			{
				this.ExpandURLFinished(this, e);
			}
		}

		public Uri ShortenURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			this.ShortenServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = "http://tweetburner.com/links";

			NameValueCollection nvc = new NameValueCollection();
			nvc.Add("link[url]", url.ToString());

			string result = Encoding.UTF8.GetString(this.ShortenServiceConnector.UploadValues(api, nvc));

			return new Uri(result);
		}

		public Uri ExpandURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			if (url.Host != "twurl.nl")
			{
				throw new ArgumentException("twurl.nlによって短縮されたURLではありません。", "url");
			}

			this.ExpandServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			this.ExpandServiceConnector.DownloadString(url);

			var info = typeof(WebClient).GetField("m_WebResponse", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
			var response = info.GetValue(this.ExpandServiceConnector) as HttpWebResponse;

			return response.ResponseUri;
		}

		public void BeginShortenURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			this.ShortenServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = String.Format("http://tinyurl.com/api-create.php?url={0}", url);

			this.ShortenServiceConnector.DownloadStringCompleted +=
				(sender, e) =>
				{
					if (e.Error != null)
					{
						this.OnShortenURLFinished(new ShortenURLFinishedEventArgs(url, null, e.Error));
					}
					else
					{
						this.OnShortenURLFinished(new ShortenURLFinishedEventArgs(url, new Uri(e.Result), null));
					}
				};

			this.ShortenServiceConnector.DownloadStringAsync(new Uri(api));
		}

		public void EndShortenURL()
		{
			this.ShortenServiceConnector.CancelAsync();
		}

		public void BeginExpandURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			if (url.Host != "twurl.nl")
			{
				throw new ArgumentException("twurl.nlによって短縮されたURLではありません。", "url");
			}

			this.ExpandServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = String.Format("http://ux.nu/hugeurl?url={0}", url);

			this.ExpandServiceConnector.DownloadStringCompleted +=
				(sender, e) =>
				{
					if (e.Error != null)
					{
						this.OnExpandURLFinished(new ExpandURLFinishedEventArgs(url, null, e.Error));
					}
					else
					{
						var info = typeof(WebClient).GetField("m_WebResponse", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
						var response = info.GetValue(this.ExpandServiceConnector) as HttpWebResponse;

						this.OnExpandURLFinished(new ExpandURLFinishedEventArgs(url, response.ResponseUri, null));
					}
				};

			this.ExpandServiceConnector.DownloadStringAsync(new Uri(api));
		}

		public void EndExpandURL()
		{
			this.ExpandServiceConnector.CancelAsync();
		}

		public void Dispose()
		{
			this.ShortenServiceConnector.Dispose();
			this.ExpandServiceConnector.Dispose();
		}

		public event ShortenURLFinishedEventHandler ShortenURLFinished;

		public event ExpandURLFinishedEventHandler ExpandURLFinished;
	}

	/// <summary>
	/// goo.glでのURL短縮用のメソッドを提供します。
	/// </summary>
	public sealed class GooGlURLShortener : IURLShorten
	{
		private WebClient ShortenServiceConnector;
		private WebClient ExpandServiceConnector;

		public GooGlURLShortener()
		{
		}

		private void OnShortenURLFinished(ShortenURLFinishedEventArgs e)
		{
			if (this.ShortenURLFinished != null)
			{
				this.ShortenURLFinished(this, e);
			}
		}

		private void OnExpandURLFinished(ExpandURLFinishedEventArgs e)
		{
			if (this.ExpandURLFinished != null)
			{
				this.ExpandURLFinished(this, e);
			}
		}

		public Uri ShortenURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			this.ShortenServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = "http://goo.gl/api/url";

			NameValueCollection nvc = new NameValueCollection();
			nvc.Add("user", "toolbar@google.com");
			nvc.Add("url", url.ToString());
			nvc.Add("auth_token", this.GenerateAuthToken(url.ToString()));

			string result = Encoding.UTF8.GetString(this.ShortenServiceConnector.UploadValues(api, nvc));

			Regex pattern = new Regex("{\"short_url\":\"(.*?)\",");
			Match match = pattern.Match(result);

			if (match.Success)
			{
				return new Uri(match.Groups[1].Value);
			}
			else
			{
				return null;
			}
		}

		public Uri ExpandURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			if (url.Host != "goo.gl")
			{
				throw new ArgumentException("goo.glによって短縮されたURLではありません。", "url");
			}

			this.ExpandServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			this.ExpandServiceConnector.DownloadString(url);

			var info = typeof(WebClient).GetField("m_WebResponse", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
			var response = info.GetValue(this.ExpandServiceConnector) as HttpWebResponse;

			return response.ResponseUri;
		}

		public void BeginShortenURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			this.ShortenServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = "http://goo.gl/api/url";

			NameValueCollection nvc = new NameValueCollection();
			nvc.Add("user", "toolbar@google.com");
			nvc.Add("url", url.ToString());
			nvc.Add("auth_token", this.GenerateAuthToken(url.ToString()));

			this.ShortenServiceConnector.UploadValuesCompleted +=
				(sender, e) =>
				{
					if (e.Error != null)
					{
						this.OnShortenURLFinished(new ShortenURLFinishedEventArgs(url, null, e.Error));
					}
					else
					{
						string result = Encoding.UTF8.GetString(e.Result);

						Regex pattern = new Regex("{\"short_url\":\"(.*?)\",");
						Match match = pattern.Match(result);

						if (match.Success)
						{
							this.OnShortenURLFinished(new ShortenURLFinishedEventArgs(url, new Uri(match.Groups[1].Value), null));
						}
						else
						{
							this.OnShortenURLFinished(new ShortenURLFinishedEventArgs(url, null, null));
						}
					}
				};

			this.ShortenServiceConnector.UploadValuesAsync(new Uri(api), nvc);
		}

		public void EndShortenURL()
		{
			this.ShortenServiceConnector.CancelAsync();
		}

		public void BeginExpandURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			if (url.Host != "goo.gl")
			{
				throw new ArgumentException("goo.glによって短縮されたURLではありません。", "url");
			}

			this.ExpandServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = String.Format("http://ux.nu/hugeurl?url={0}", url);

			this.ExpandServiceConnector.DownloadStringCompleted +=
				(sender, e) =>
				{
					if (e.Error != null)
					{
						this.OnExpandURLFinished(new ExpandURLFinishedEventArgs(url, null, e.Error));
					}
					else
					{
						var info = typeof(WebClient).GetField("m_WebResponse", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
						var response = info.GetValue(this.ExpandServiceConnector) as HttpWebResponse;

						this.OnExpandURLFinished(new ExpandURLFinishedEventArgs(url, response.ResponseUri, null));
					}
				};

			this.ExpandServiceConnector.DownloadStringAsync(new Uri(api));
		}

		public void EndExpandURL()
		{
			this.ExpandServiceConnector.CancelAsync();
		}

		public void Dispose()
		{
			this.ShortenServiceConnector.Dispose();
			this.ExpandServiceConnector.Dispose();
		}

		private string GenerateAuthToken(string b)
		{
			long i = __e(b);

			i = i >> 2 & 1073741823;
			i = i >> 4 & 67108800 | i & 63;
			i = i >> 4 & 4193280 | i & 1023;
			i = i >> 4 & 245760 | i & 16383;

			long h = __f(b);
			long k = (i >> 2 & 15) << 4 | h & 15;
			k |= (i >> 6 & 15) << 12 | (h >> 8 & 15) << 8;
			k |= (i >> 10 & 15) << 20 | (h >> 16 & 15) << 16;
			k |= (i >> 14 & 15) << 28 | (h >> 24 & 15) << 24;

			return "7" + __d(k);
		}

		private long __c(long a, long b, long c)
		{
			long l = 0;
			l += (a & 4294967295);
			l += (b & 4294967295);
			l += (c & 4294967295);

			return l;
		}

		private long __c(long a, long b, long c, long d)
		{
			long l = 0;
			l += (a & 4294967295);
			l += (b & 4294967295);
			l += (c & 4294967295);
			l += (d & 4294967295);

			return l;
		}

		private string __d(long l)
		{
			string ll = l.ToString();
			string m = (l > 0 ? l : l + 4294967296).ToString();
			bool n = false;
			long o = 0;

			for (int p = m.Length - 1; p >= 0; --p)
			{
				long q = Int64.Parse(m[p].ToString());

				if (n)
				{
					q *= 2;
					o += (long)Math.Floor((double)q / 10) + q % 10;
				}
				else
				{
					o += q;
				}

				n = !n;
			}

			long mm = o % 10;

			o = 0;

			if (mm != 0)
			{
				o = 10 - mm;

				if (ll.Length % 2 == 1)
				{
					if (o % 2 == 1) o += 9;
					o /= 2;
				}
			}

			m = o.ToString();
			m += ll;

			return m;
		}

		private long __e(string l)
		{
			long m = 5381;
			for (int o = 0; o < l.Length; o++)
			{
				m = __c(m << 5, m, (long)l[o]);
			}
			return m;
		}

		private long __f(string l)
		{
			long m = 0;
			for (int o = 0; o < l.Length; o++)
			{
				m = __c(l[o], m << 6, m << 16, -m);
			}
			return m;
		}

		public event ShortenURLFinishedEventHandler ShortenURLFinished;

		public event ExpandURLFinishedEventHandler ExpandURLFinished;
	}

	/// <summary>
	/// p.tlでのURL短縮用のメソッドを提供します。
	/// </summary>
	public sealed class PTlURLShortener : IURLShorten
	{
		private WebClient ShortenServiceConnector;
		private WebClient ExpandServiceConnector;

		private string ApiKey;

		public PTlURLShortener(string apiKey)
		{
			this.ApiKey = apiKey;
		}

		private void OnShortenURLFinished(ShortenURLFinishedEventArgs e)
		{
			if (this.ShortenURLFinished != null)
			{
				this.ShortenURLFinished(this, e);
			}
		}

		private void OnExpandURLFinished(ExpandURLFinishedEventArgs e)
		{
			if (this.ExpandURLFinished != null)
			{
				this.ExpandURLFinished(this, e);
			}
		}

		public Uri ShortenURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			this.ShortenServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = "http://p.tl/api/api_simple.php";

			NameValueCollection nvc = new NameValueCollection();
			nvc.Add("url", url.ToString());
			nvc.Add("key", this.ApiKey);

			this.ShortenServiceConnector.QueryString = nvc;
			string result = Encoding.UTF8.GetString(this.ShortenServiceConnector.UploadValues(api, nvc));

			Regex pattern = new Regex("{\"status\":\"(.*?)\",\"long_url\":\"(.*?)\",\"short_url\":\"(.*?)\",\"counter\":(.*?)}");
			Match match = pattern.Match(result);

			if (match.Success)
			{
				return new Uri(new string(match.Groups[3].Value.Where(c => c != '\\').ToArray()));
			}
			else
			{
				return null;
			}
		}

		public Uri ExpandURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			if (url.Host != "p.tl")
			{
				throw new ArgumentException("p.tlによって短縮されたURLではありません。", "url");
			}

			this.ExpandServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			this.ExpandServiceConnector.DownloadString(url);

			var info = typeof(WebClient).GetField("m_WebResponse", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
			var response = info.GetValue(this.ExpandServiceConnector) as HttpWebResponse;

			return response.ResponseUri;
		}

		public void BeginShortenURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			this.ShortenServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = String.Format("http://tinyurl.com/api-create.php?url={0}", url);

			NameValueCollection nvc = new NameValueCollection();
			nvc.Add("url", url.ToString());
			nvc.Add("key", this.ApiKey);

			this.ShortenServiceConnector.UploadValuesCompleted +=
				(sender, e) =>
				{
					if (e.Error != null)
					{
						this.OnShortenURLFinished(new ShortenURLFinishedEventArgs(url, null, e.Error));
					}
					else
					{
						string result = Encoding.UTF8.GetString(e.Result);
						Regex pattern = new Regex("{\"status\":\"(.*?)\",\"long_url\":\"(.*?)\",\"short_url\":\"(.*?)\",\"counter\":(.*?)}");
						Match match = pattern.Match(result);

						if (match.Success)
						{
							this.OnShortenURLFinished(new ShortenURLFinishedEventArgs(url, new Uri(new string(match.Groups[3].Value.Where(c => c != '\\').ToArray())), null));
						}
						else
						{
							this.OnShortenURLFinished(new ShortenURLFinishedEventArgs(url, null, null));
						}
					}
				};

			this.ShortenServiceConnector.QueryString = nvc;
			this.ShortenServiceConnector.UploadValuesAsync(new Uri(api), nvc);
		}

		public void EndShortenURL()
		{
			this.ShortenServiceConnector.CancelAsync();
		}

		public void BeginExpandURL(Uri url)
		{
			if (url == null)
			{
				throw new ArgumentNullException("url");
			}

			if (url.Host != "p.tl")
			{
				throw new ArgumentException("p.tlによって短縮されたURLではありません。", "url");
			}

			this.ExpandServiceConnector = new WebClient()
			{
				Encoding = Encoding.UTF8
			};

			string api = String.Format("http://ux.nu/hugeurl?url={0}", url);

			this.ExpandServiceConnector.DownloadStringCompleted +=
				(sender, e) =>
				{
					if (e.Error != null)
					{
						this.OnExpandURLFinished(new ExpandURLFinishedEventArgs(url, null, e.Error));
					}
					else
					{
						var info = typeof(WebClient).GetField("m_WebResponse", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
						var response = info.GetValue(this.ExpandServiceConnector) as HttpWebResponse;

						this.OnExpandURLFinished(new ExpandURLFinishedEventArgs(url, response.ResponseUri, null));
					}
				};

			this.ExpandServiceConnector.DownloadStringAsync(new Uri(api));
		}

		public void EndExpandURL()
		{
			this.ExpandServiceConnector.CancelAsync();
		}

		public void Dispose()
		{
			this.ShortenServiceConnector.Dispose();
			this.ExpandServiceConnector.Dispose();
		}

		public event ShortenURLFinishedEventHandler ShortenURLFinished;

		public event ExpandURLFinishedEventHandler ExpandURLFinished;
	}

	/// <summary>
	/// IURLShortenを実装するオブジェクトを利用するための一連のstaticメソッドを提供します。
	/// </summary>
	public static class URLShortener
	{
		public static Uri ShortenByBitLy(this Uri url, string user, string apiKey)
		{
			using (BitLyURLShortener sh = new BitLyURLShortener(user, apiKey))
			{
				return sh.ShortenURL(url);
			}
		}

		public static Uri ShortenByJMp(this Uri url, string user, string apiKey)
		{
			using (JMpURLShortener sh = new JMpURLShortener(user, apiKey))
			{
				return sh.ShortenURL(url);
			}
		}

		public static Uri ShortenByUxNu(this Uri url)
		{
			using (UxNuURLShortener sh = new UxNuURLShortener())
			{
				return sh.ShortenURL(url);
			}
		}

		public static Uri ShortenByIsGd(this Uri url)
		{
			using (IsGdURLShortener sh = new IsGdURLShortener())
			{
				return sh.ShortenURL(url);
			}
		}

		public static Uri ShortenByTinyurlCom(this Uri url)
		{
			using (TinyurlComURLShortener sh = new TinyurlComURLShortener())
			{
				return sh.ShortenURL(url);
			}
		}

		public static Uri ShortenByTwurlNl(this Uri url)
		{
			using (TwurlNlURLShortener sh = new TwurlNlURLShortener())
			{
				return sh.ShortenURL(url);
			}
		}

		public static Uri ShortenByGooGl(this Uri url)
		{
			using (GooGlURLShortener sh = new GooGlURLShortener())
			{
				return sh.ShortenURL(url);
			}
		}

		public static Uri ShortenByPTl(this Uri url, string apiKey)
		{
			using (PTlURLShortener sh = new PTlURLShortener(apiKey))
			{
				return sh.ShortenURL(url);
			}
		}

		public static Uri ExpandFromBitLy(this Uri url, string user, string apiKey)
		{
			using (BitLyURLShortener sh = new BitLyURLShortener(user, apiKey))
			{
				return sh.ExpandURL(url);
			}
		}

		public static Uri ExpandFromJMp(this Uri url, string user, string apiKey)
		{
			using (JMpURLShortener sh = new JMpURLShortener(user, apiKey))
			{
				return sh.ExpandURL(url);
			}
		}

		public static Uri ExpandFromUxNu(this Uri url)
		{
			using (UxNuURLShortener sh = new UxNuURLShortener())
			{
				return sh.ExpandURL(url);
			}
		}

		public static Uri ExpandFromIsGd(this Uri url)
		{
			using (IsGdURLShortener sh = new IsGdURLShortener())
			{
				return sh.ExpandURL(url);
			}
		}

		public static Uri ExpandFromTinyurlCom(this Uri url)
		{
			using (TinyurlComURLShortener sh = new TinyurlComURLShortener())
			{
				return sh.ExpandURL(url);
			}
		}

		public static Uri ExpandFromTwurlNl(this Uri url)
		{
			using (TwurlNlURLShortener sh = new TwurlNlURLShortener())
			{
				return sh.ExpandURL(url);
			}
		}

		public static Uri ExpandFromGooGl(this Uri url)
		{
			using (GooGlURLShortener sh = new GooGlURLShortener())
			{
				return sh.ExpandURL(url);
			}
		}

		public static Uri ExpandFromPTl(this Uri url, string apiKey)
		{
			using (PTlURLShortener sh = new PTlURLShortener(apiKey))
			{
				return sh.ExpandURL(url);
			}
		}
	}
}
