using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Description;
using System.Web.Http.Routing;

namespace ApiDocConstraint.ApiVersion
{
    public class VersionedApiExplorer<TVersionConstraint> : IApiExplorer 
    {
        private IApiExplorer _innerApiExplorer;
        private HttpConfiguration _configuration;
        private Lazy<Collection<ApiDescription>> _apiDescriptions;
        private MethodInfo _apiDescriptionPopulator;

        public  VersionedApiExplorer(IApiExplorer apiExplorer, HttpConfiguration configuration)
        {
            _innerApiExplorer = apiExplorer;
            _configuration = configuration;
            _apiDescriptions = new Lazy<Collection<ApiDescription>>(
                new Func<Collection<ApiDescription>>(Init));
        }      
        
        public Collection<ApiDescription> ApiDescriptions
        {
            get { return _apiDescriptions.Value; }
        }
        
        private Collection<ApiDescription> Init()
        {
            var descriptions = _innerApiExplorer.ApiDescriptions;
        
            var controllerSelector = _configuration.Services.GetHttpControllerSelector();
            var controllerMappings = controllerSelector.GetControllerMapping();

            var flatRoutes = FlattenRoutes(_configuration.Routes);
            var result = new Collection<ApiDescription>();

            foreach (var description in descriptions)
            {
                result.Add(description);

                if (controllerMappings != null && description.Route.Constraints.Any(c => c.Value is TVersionConstraint))
                {
                    var matchingRoutes = flatRoutes.Where(r => r.RouteTemplate == description.Route.RouteTemplate && r != description.Route);

                    foreach(var route  in matchingRoutes)
                        GetRouteDescriptions(route,result);
                }
            }
            return result;
        }

        private void GetRouteDescriptions(IHttpRoute route,Collection<ApiDescription> apiDescriptions)
        {
            var actionDescriptor = route.DataTokens["actions"] as IEnumerable<HttpActionDescriptor>;

            if (actionDescriptor != null && actionDescriptor.Count() > 0)
                GetPopulateMethod().Invoke(_innerApiExplorer, new object[] { actionDescriptor.First(), route, route.RouteTemplate, apiDescriptions });   
        }

        private MethodInfo GetPopulateMethod()
        {
            if (_apiDescriptionPopulator == null)
                _apiDescriptionPopulator  = _innerApiExplorer.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault(
                   m => m.Name == "PopulateActionDescriptions" && m.GetParameters().Length == 4);

            return _apiDescriptionPopulator;
        }

        public static IEnumerable<IHttpRoute> FlattenRoutes(IEnumerable<IHttpRoute> routes)
        {
            var flatRoutes = new List<HttpRoute>();

            foreach (var route in routes)
            {
                if (route is HttpRoute)
                    yield return route;

                var subRoutes = route as IReadOnlyCollection<IHttpRoute>;
                if (subRoutes != null)
                    foreach (IHttpRoute subRoute in FlattenRoutes(subRoutes))
                        yield return subRoute;
            }
        }   
    }
}