/// <summary>Adds everything possible to target from another source.
        /// This may include new comments, full comments, larger text etc.</summary>
        /// <param name="target">Object to add data to.</param>
        /// <param name="otherSource">Object to use data from.</param>
        public bool AddData(EntryPage target, EntryPage otherSource)
        {
            bool updated = false;

            // Check arguments.
            if(target == null || otherSource == null)
                throw new ArgumentNullException();


            // Entry.
            log.DebugFormat("Updating content for entry page '{0}' from other source.", target);
            updated |= _entryHelper.UpdateWith(target.Entry, otherSource.Entry);

            // Comments.
            log.DebugFormat("Updating comments for entry page '{0}' from other source.", target);
            updated |= _repliesHelper.MergeFrom(target, otherSource);

            // CommentPages.
            if(target.CommentPages != null && !CommentPages.IsEmpty(target.CommentPages))
            {
                log.DebugFormat("Cleaning comment pages information for entry page '{0}'.", target);
                target.CommentPages = CommentPages.Empty;
                updated = true;
            }

            return updated;
        }
        public bool AbsorbAllData(EntryPage freshSource, ILJClientData clientData, ref EntryPage dumpData)
        {
            bool appliedAnything = false;

            if(dumpData == null)
            {
                dumpData = new EntryPage();
                appliedAnything = true;
            }

            appliedAnything |= _entryPageHelper.AddData(dumpData, freshSource);

            // TryGet all comments.
            EntryPage[] otherPages = _otherPagesLoader.LoadOtherCommentPages(freshSource.CommentPages, clientData);

            foreach(EntryPage pageX in otherPages)
                appliedAnything |= _entryPageHelper.AddData(dumpData, pageX);

            while(true)
            {
                IEnumerable<Comment> allFoldedComments = _repliesHelper.EnumerateRequiringFullUp(dumpData.Replies);
                IEnumerator<Comment> enumerator = allFoldedComments.GetEnumerator();

                int foldedCommentsLeft = 0;
                Comment c = null;

                while(enumerator.MoveNext())
                {
                    foldedCommentsLeft++;
                    if(c == null)
                        c = enumerator.Current;
                }

                // How many comments left?
                log.Info(String.Format("Folded comments left: {0}.", foldedCommentsLeft));
                if(foldedCommentsLeft == 0)
                    break;
                
                LiveJournalTarget commentTarget = LiveJournalTarget.FromString(c.Url);
                EntryPage commentPage = GetFrom(commentTarget, clientData);
                Comment fullVersion = commentPage.Replies.Comments[0];
                if(fullVersion.IsFull == false)
                {
                    // This should be a suspended user.
                    log.Info(String.Format("Comment {0} seems to be from a suspended user.", c));
                    c.IsSuspendedUser = true;
                    continue;
                }

                log.Info(String.Format("Merging comment data for comment {0}.", c));
                appliedAnything |= _repliesHelper.MergeFrom(c, fullVersion);
            }

            return appliedAnything;
        }
        private void AssertAuthorCommentsArePicked(EntryPage ep, List<Comment[]> ret)
        {
            string authorUsername = ep.Entry.Poster.Username;
            Comment[] authorCommentsCount = _repliesHelper.EnumerateAll(ep.Replies).Where(z => z.Poster.Username == authorUsername).ToArray();
            Comment[] authorCommentsPicked = ret.SelectMany(z => z).Where(z => z.Poster.Username == authorUsername).ToArray();
            Comment[] authorCommentsLeftBehind = authorCommentsCount.Except(authorCommentsPicked).ToArray();

            if(authorCommentsLeftBehind.Length != 0)
            {
                // Watchdog barks.
                string message = String.Format("Author comments with ids {0} were left behind when picking.", String.Join(", ", authorCommentsLeftBehind.Select(z => z.Id)));
                log.Error(message);
                throw new ApplicationException(message);
            }
        }
        public List<Comment[]> Pick(EntryPage ep)
        {
            string authorUsername = ep.Entry.Poster.Username;

            // Get threads from each root comment.
            List<Comment[]> ret = ep.Replies.Comments
                                    .AsParallel().AsOrdered()
                                    .Select(rootComment => ExtractThreads(rootComment, authorUsername))
                                    .SelectMany(a => a)
                                    .ToList();

            // Make sure all author comments were picked.
            AssertAuthorCommentsArePicked(ep, ret);

            return ret;
        }
        public string Serialize(EntryPage ep)
        {
            UTF8Encoding enc = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);

            XmlWriterSettings settings = new XmlWriterSettings();
            settings.OmitXmlDeclaration = true;
            settings.Indent = true;
            settings.ConformanceLevel = ConformanceLevel.Auto;
            settings.Encoding = enc;

            XmlSerializerNamespaces names = new XmlSerializerNamespaces();
            names.Add("", "");

            MemoryStream ms = new MemoryStream();
            XmlWriter writer = XmlWriter.Create(ms, settings);
            XmlSerializer sr = new XmlSerializer(typeof(EntryPage));

            sr.Serialize(writer, ep, names);

            return enc.GetString(ms.ToArray());
        }