using System;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace Prise.Proxy
{
    public abstract class ResultConverter : IResultConverter
    {
        private bool disposed = false;
        public abstract object Deserialize(Type localType, Type remoteType, object value);
        public object ConvertToLocalType(Type localType, Type remoteType, object value)
        {
            return Deserialize(localType, remoteType, value);
        }

        public object ConvertToLocalTypeAsync(Type localType, Type remoteType, Task task)
        {
            var taskResultType = localType.GenericTypeArguments != null && localType.GenericTypeArguments.Any() ? localType.GenericTypeArguments[0] : null;
            var taskCompletionSource = new TaskCompletionSource(taskResultType ?? typeof(object));

            task.ContinueWith(t =>
            {
                if (t.IsCanceled)
                    taskCompletionSource.TrySetCanceled();
                else if (t.IsFaulted)
                    taskCompletionSource.TrySetException(t.Exception);
                else if (taskResultType == null)
                    taskCompletionSource.TrySetResult(null);
                else
                {
                    var property = t.GetType()
                                  .GetTypeInfo()
                                  .GetProperties()
                                  .FirstOrDefault(p => p.Name == "Result");

                    if (property != null)
                    {
                        var value = Deserialize(localType, remoteType, property.GetValue(task));
                        taskCompletionSource.TrySetResult(value);
                    }
                }
            });

            return taskCompletionSource.Task;
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed && disposing)
            {
                // Nothing to do here
            }
            this.disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}