using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace WebApi.Formatters
{
    public class MultipartFormDataMediaTypeFormatter : MediaTypeFormatter
    {
        private const BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty;

        public MultipartFormDataMediaTypeFormatter()
        {
            SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
        }

        public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
        {
            return ReadFromStreamAsync(type, readStream, content, formatterLogger, CancellationToken.None);
        }
        public override async Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            try
            {
                var obj = Activator.CreateInstance(type);
                var parts = await content.ReadAsMultipartAsync(cancellationToken);
                foreach (var item in parts.Contents)
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    var partName = item.Headers.ContentDisposition.Name;
                    if (string.IsNullOrEmpty(partName))
                    {
                        if (parts.Contents.Count == 1)
                        {
                            return await item.ReadAsAsync(type, cancellationToken);
                        }
                        else // multiple parts, no name -> error
                        {
                            formatterLogger.LogError("", "The content part does not have a name");
                        }
                    }
                    else // part defined its destination
                    {
                        var property = obj.GetType().GetProperty(partName, flags);
                        if (property == null)
                        {
                            formatterLogger.LogError(partName, "The model does not have this property");
                        }
                        else // we found the property for the name
                        {
                            var part = await item.ReadAsAsync(property.PropertyType, cancellationToken);

                            try { property.SetValue(obj, part); }
                            catch (Exception ex) { formatterLogger.LogError(partName, ex); }
                        }
                    }
                }
                return obj;
            }
            catch (OperationCanceledException)
            {
                throw;
            }
            catch (Exception ex)
            {
                formatterLogger.LogError("", ex);
                throw;
            }
        }

        // we can read but cannot write: it would be a much greater project
        public override bool CanReadType(Type type) { return true; }
        public override bool CanWriteType(Type type) { return false; }
    }
}