﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ScriptFlow
{
    public abstract class ScriptFlowTask
    {
        protected ScriptFlowTask()
        {

        }

        public virtual string GetDefaultDescription()
        {
            return this.GetType().Name;
        }

        public virtual IEnumerable<ArgumentDescriptor> GetArgumentDescriptions()
        {
            Type typeBaseArgument = typeof(ArgumentBase);
            Type typeInArgument = typeof(InArgument);
            Type typeOutArgument = typeof(OutArgument);
            foreach (var actProperty in this.GetType().GetTypeInfo().DeclaredProperties)
            {
                // Check for argument type
                ArgumentType argType = ArgumentType.In;
                if(actProperty.PropertyType == typeInArgument) { }
                else if(actProperty.PropertyType == typeOutArgument) { argType = ArgumentType.Out;}
                else { continue; }

                // Return the descriptor for this property
                yield return new ArgumentDescriptor(
                    this, this, actProperty, argType);
            }
        }

        /// <summary>
        /// Called by <see cref="ScriptFlowStory"/> during execution of a script flow.
        /// </summary>
        public Task ExecuteAsync(ScriptFlowContext context, CancellationToken cancelToken = default(CancellationToken))
        {
            var taskFinishedSource = new TaskCompletionSource<object>();
            TaskExecutionContext taskContext = new TaskExecutionContext(taskFinishedSource, cancelToken);

            // Get current and target SynchronizationContext
            var currentSyncContext = SynchronizationContext.Current;
            var ownSyncContext = GetSyncContext();

            // Dispatch execution to correct SynchronizationContext
            if ((currentSyncContext == ownSyncContext) ||
                (ownSyncContext == null))
            {
                // We are on the correct thread, so execut synchronous
                ExecuteInternal(context, taskContext);
            }
            else
            {
                // We are on the wrong thread, so post execution to the correct one
                ownSyncContext.Post(
                    new SendOrPostCallback((object arg) => ExecuteInternal(context, taskContext)),
                    null);
            }

            return taskFinishedSource.Task;
        }

        protected abstract void ExecuteInternal(ScriptFlowContext context, TaskExecutionContext taskContext);

        protected virtual SynchronizationContext GetSyncContext()
        {
            return null;
        }
    }
}
