using System;
using System.Collections.Generic;
using System.Text;
using OpenLiveWriter.Extensibility.BlogClient;
using BlogRunner.Core.Config;
using System.Xml;
using System.Threading;
using System.Net;
using System.Text.RegularExpressions;

namespace BlogRunner.Core
{
    public abstract class Test
    {
        protected const string YES = "Yes";
        protected const string NO = "No";

        const string NAMESPACE_BLOGRUNNER = "http://writer.live.com/blogrunner/2007";

        public abstract void DoTest(Blog blog, IBlogClient blogClient, ITestResults results);

        public virtual void DoTest(Blog blog, IBlogClient blogClient, XmlElement providerEl)
        {
            TestResultImpl results = new TestResultImpl();
            DoTest(blog, blogClient, results);
            results.ForEach(delegate(string key, string value) {
                    XmlElement optionsEl = (XmlElement) providerEl.SelectSingleNode("options");
                    if (optionsEl == null)
                    {
                        optionsEl = providerEl.OwnerDocument.CreateElement("options");
                        providerEl.AppendChild(optionsEl);
                    }

                    XmlElement el = (XmlElement) optionsEl.SelectSingleNode(key);
                    if (el == null)
                    {
                        el = providerEl.OwnerDocument.CreateElement(key);
                        optionsEl.AppendChild(el);
                    }

                    if (!el.HasAttribute("readonly", NAMESPACE_BLOGRUNNER))
                    {
                        el.InnerText = value;
                    }
                });
        }

        static Test()
        {
            CleanUpPosts = true;
        }

        /// <summary>
        /// Retries action until either it returns true or the timeout time elapses.
        /// </summary>
        protected static void RetryUntilTimeout(TimeSpan timeout, TimeoutAction action)
        {
            DateTime due = DateTime.UtcNow + timeout;
            do
            {
                if (action())
                    return;
            }
            while (DateTime.UtcNow < due);
            throw new TimeoutException("The operation has timed out");
        }
        protected delegate bool TimeoutAction();

        protected static bool CleanUpPosts { get; set; }
    }

    public abstract class PostTest : Test
    {
        protected internal abstract void PreparePost(BlogPost blogPost, ref bool? publish);
        protected internal abstract void HandleResult(string homepageHtml, ITestResults results);

        /// <summary>
        /// Return true if the timeout condition was handled. False means
        /// the caller should deal with the timeout (by throwing TimeoutException).
        /// </summary>
        protected internal virtual bool HandleTimeout(TimeoutException te, ITestResults results)
        {
            return false;
        }

        protected virtual TimeSpan TimeoutDuration
        {
            get { return TimeSpan.FromMinutes(2.0); }
        }

        public sealed override void DoTest(Blog blog, IBlogClient blogClient, ITestResults results)
        {
            BlogPost blogPost = new BlogPost();
            bool? publish = null;
            PreparePost(blogPost, ref publish);

            string token = BlogUtil.ShortGuid;
            blogPost.Title = token + ":" + blogPost.Title;

            string etag;
            XmlDocument remotePost;
            string postId = blogClient.NewPost(blog.BlogId, blogPost, null, publish ?? true, out etag, out remotePost);

            using (WebClient webClient = new WebClient())
            {
                try
                {
                    RetryUntilTimeout(TimeoutDuration, delegate {
                        string html = Encoding.ASCII.GetString(webClient.DownloadData(blog.HomepageUrl));

                        if (html.Contains(token))
                        {
                            HandleResult(html, results);
                            return true;
                        }

                        Thread.Sleep(1000);
                        return false;
                    });
                }
                catch (TimeoutException te)
                {
                    if (!HandleTimeout(te, results))
                        throw;
                }
            }

            if (postId != null && CleanUpPosts)
                blogClient.DeletePost(blog.BlogId, postId, false);
        }
    }

    public abstract class BodyContentPostTest : PostTest
    {
        public abstract string BodyContentString { get; }
        public abstract void HandleContentResult(string result, ITestResults results);

        private string guid1, guid2;

        protected internal sealed override void PreparePost(BlogPost blogPost, ref bool? publish)
        {
            guid1 = BlogUtil.ShortGuid;
            guid2 = BlogUtil.ShortGuid;

            blogPost.Contents += "\r\n<br />\r\n" + guid1 + BodyContentString + guid2;
        }

        protected internal sealed override void HandleResult(string homepageHtml, ITestResults results)
        {
            Regex regex = new Regex(Regex.Escape(guid1) + "(.*?)" + Regex.Escape(guid2));
            Match m = regex.Match(homepageHtml);
            string result;
            if (!m.Success)
                result = null;
            else
                result = m.Groups[1].Value;
            HandleContentResult(result, results);
        }
    }

    public abstract class RoundtripTest : Test
    {
        protected internal abstract void PreparePost(Blog blog, IBlogClient blogClient, BlogPost blogPost, ref bool? publish);
        protected internal abstract void HandleResult(BlogPost blogPost, ITestResults results);

        public sealed override void DoTest(Blog blog, IBlogClient blogClient, ITestResults results)
        {
            BlogPost blogPost = new BlogPost();
            bool? publish = null;
            PreparePost(blog, blogClient, blogPost, ref publish);

            string token = BlogUtil.ShortGuid;
            blogPost.Title = token + ":" + blogPost.Title;

            string etag;
            XmlDocument remotePost;
            string postId = blogClient.NewPost(blog.BlogId, blogPost, null, publish ?? true, out etag, out remotePost);
            BlogPost newPost = blogClient.GetPost(blog.BlogId, postId);
            HandleResult(newPost, results);
            if (postId != null && CleanUpPosts)
                blogClient.DeletePost(blog.BlogId, postId, false);
        }
    }
}
