using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Xml.Linq;

namespace Azyobuzi.Twirunrun
{
    public static class ShortUri
    {
        static ShortUri()
        {
            using (var wc = new WebClient())
            {
                ShortUriList =
                        XElement.Parse(wc.DownloadString("http://azyobuzinet.ohitashi.com/software/twirunrun/short_uri_list.xml"))
                        .Elements("uri").Select(xe => xe.Value)
                        .AsParallel();
            }
        }

        private static ParallelQuery<string> ShortUriList;

        private static Dictionary<string, string> UriCache = new Dictionary<string, string>();

        private static object lockObj = new object();

        public static string Expand(string original)
        {
            lock (lockObj)
            {
                if (UriCache.Keys.AsParallel().Contains(original))
                {
                    return UriCache[original];
                }

                if (ShortUriList.Contains(new Uri(original).Host))
                {
                    var req = (HttpWebRequest)WebRequest.Create(original);
                    req.AllowAutoRedirect = false;
                    req.Method = "HEAD";
                    req.Timeout = 5000;
                    using (var res = req.GetResponse())
                    {
                        if (res.Headers.AllKeys.Contains("Location"))
                        {
                            string redirectTo = res.Headers["Location"];
                            UriCache.Add(original, redirectTo);
                            return redirectTo;
                        }
                    }
                }
                return original;
            }
        }

        public static string Shorten(string original)
        {
            var service = ShortenUriService.FromName(Settings.Instance.SelectedShortenService);
            if (service == null)
                throw new InvalidOperationException("SelectedShortenServiceが正しくありません。");
            var result = service.Shorten(original);
            if (result != original)
            {
                lock (lockObj)
                {
                    UriCache.Add(result, original);
                }
            }
            return result;
        }
    }

    public class ShortenUriService
    {
        private ShortenUriService() { }

        public Func<string, string> Shorten { get; private set; }

        public string Name { get; private set; }

        public override string ToString()
        {
            return Name;
        }

        public static IEnumerable<ShortenUriService> Services
        {
            get
            {
                return
                    typeof(ShortenUriService)
                    .GetMembers(BindingFlags.Static | BindingFlags.Public)
                    //.Where(m => (m.MemberType & MemberTypes.Property) == MemberTypes.Property)
                    //.Cast<PropertyInfo>()
                    .OfType<PropertyInfo>()
                    .Where(pi => pi.PropertyType == typeof(ShortenUriService))
                    .Select(pi => (ShortenUriService)pi.GetValue(null, null));
            }
        }

        /// <summary>名前から取得。存在しない場合はnull</summary>
        public static ShortenUriService FromName(string name)
        {
            return Services.SingleOrDefault(o => o.Name.Equals(name, StringComparison.CurrentCultureIgnoreCase));
        }

        private static string ShortenWithBitly(string original, string domain)
        {
            try
            {
                var args = new NameValueCollection();
                if (string.IsNullOrWhiteSpace(Settings.Instance.bitlyId) || string.IsNullOrWhiteSpace(Settings.Instance.bitlyApiKey))
                {
                    //既定のIDを使う
                    args.Add("login", AppSettings.bitlyId);
                    args.Add("apikey", AppSettings.bitlyApiKey);
                }
                else
                {
                    args.Add("login", Settings.Instance.bitlyId);
                    args.Add("apikey", Settings.Instance.bitlyApiKey);
                }
                args.Add("longUrl", original);
                args.Add("format", "xml");
                string res;
                using (var wc = new WebClient())
                {
                    res = wc.UploadValues(string.Format("http://api.{0}/v3/shorten", domain), args).GetString();
                }
                return XElement.Parse(res).Descendants("url").First().Value;
            }
            catch
            {
                return original;
            }
        }

        static ShortenUriService()
        {
            //プロパティセット
            bitly = new ShortenUriService()
            {
                Name = "bit.ly",
                Shorten = original => ShortenWithBitly(original, "bit.ly")
            };
            jmp = new ShortenUriService()
            {
                Name = "j.mp",
                Shorten = original => ShortenWithBitly(original, "j.mp")
            };
            TinyURL = new ShortenUriService()
            {
                Name = "TinyURL.com",
                Shorten = original =>
                {
                    try
                    {
                        using (var wc = new WebClient())
                        {
                            return wc.DownloadString("http://tinyurl.com/api-create.php?url=" + original);
                        }
                    }
                    catch
                    {
                        return original;
                    }
                }
            };
            isgd = new ShortenUriService()
            {
                Name = "is.gd",
                Shorten = original =>
                {
                    try
                    {
                        var args = new NameValueCollection();
                        args.Add("URL", original);
                        string res;
                        using (var wc = new WebClient())
                        {
                            res = wc.UploadValues("http://is.gd/create.php", args).GetString();
                        }
                        return res.RegexMatch(@"(?<= id=""short_url"" value="")http://is.gd/\w+(?="")").ToString();
                    }
                    catch
                    {
                        return original;
                    }
                }
            };
        }

        public static ShortenUriService bitly { get; private set; }
        public static ShortenUriService jmp { get; private set; }
        public static ShortenUriService TinyURL { get; private set; }
        public static ShortenUriService isgd { get; private set; }
    }
}
