using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using GreenTree.Nachtragsmanagement.Core.Domain.Appendix;
using GreenTree.Nachtragsmanagement.Core.Domain.Deviation;
using GreenTree.Nachtragsmanagement.Core.Domain.Site;
using GreenTree.Nachtragsmanagement.Core.Domain.User;
using GreenTree.Nachtragsmanagement.Data;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace GreenTree.Nachtragsmanagement.Services.Test
{
    public class DbRelationService : IDbRelationService
    {
        private readonly IDbContext _idbContext;

        public DbRelationService(
            IDbContext idbContext)
        {
            _idbContext = idbContext;
        }

        #region Testing

        /// <summary>
        /// Serialize all existing entities of the current DbContext
        /// </summary>
        /// <param name="depth">The depth the serialization should dive in.</param>
        /// <param name="format">The output format.</param>
        /// <returns>Return all entities either in JSON- or XML-format.</returns>
        public List<string> GetRelations(int depth, DbRelationFormat format)
        {
            var result = new List<string>();
            var dbContext = (AppendixObjectContext)_idbContext;

            var userList = dbContext.Get<Core.Domain.User.User>().ToList();
            var deviationList = dbContext.Get<Core.Domain.Deviation.Deviation>().ToList();
            var siteList = dbContext.Get<Core.Domain.Site.Site>().ToList();
            var appendixList = dbContext.Get<Core.Domain.Appendix.Appendix>().ToList();

            if (format == DbRelationFormat.Json)
            {
                result.Add(SerializeObjectJson(userList, depth));
                result.Add(SerializeObjectJson(deviationList, depth));
                result.Add(SerializeObjectJson(siteList, depth));
                result.Add(SerializeObjectJson(appendixList, depth));
            }
            else
                result.Add(String.Empty);

            return result;
        }

        #endregion

        #region Utilities

        /// <summary>
        /// Serialize objects using CustomJsonTextWriter
        /// </summary>
        /// <param name="obj">The object to be serialized.</param>
        /// <param name="maxDepth">The maximum depth.</param>
        public static string SerializeObjectJson(object obj, int maxDepth)
        {
            using (var strWriter = new StringWriter())
            {
                using (var jsonWriter = new CustomJsonTextWriter(strWriter))
                {
                    Func<bool> include = () => jsonWriter.CurrentDepth <= maxDepth;
                    var resolver = new CustomContractResolver(include);
                    var serializer = new JsonSerializer
                    {
                        ContractResolver = resolver,
                        Formatting = Formatting.Indented,
                        ReferenceLoopHandling = ReferenceLoopHandling.Serialize
                    };
                    serializer.Serialize(jsonWriter, obj);
                }
                return strWriter.ToString();
            }
        }

        #endregion
    }

    public class CustomJsonTextWriter : JsonTextWriter
    {
        public CustomJsonTextWriter(TextWriter textWriter) : base(textWriter) { }

        public int CurrentDepth { get; private set; }

        public override void WriteStartObject()
        {
            CurrentDepth++;
            base.WriteStartObject();
        }

        public override void WriteEndObject()
        {
            CurrentDepth--;
            base.WriteEndObject();
        }
    }

    public class CustomContractResolver : DefaultContractResolver
    {
        private readonly Func<bool> _includeProperty;

        public CustomContractResolver(Func<bool> includeProperty)
        {
            _includeProperty = includeProperty;
        }

        protected override JsonProperty CreateProperty(
            MemberInfo member, MemberSerialization memberSerialization)
        {
            var property = base.CreateProperty(member, memberSerialization);
            var shouldSerialize = property.ShouldSerialize;
            property.ShouldSerialize = obj => _includeProperty() &&
                                              (shouldSerialize == null ||
                                               shouldSerialize(obj));
            return property;
        }
    }
}
