//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------

namespace System.Workflow.Runtime.Hosting
{
    using System.Runtime;
    using System.Runtime.Diagnostics;
    using System.ServiceModel;
    using System.ServiceModel.Diagnostics;
    using System.ServiceModel.Dispatcher;
    using System.Threading;
    using System.Diagnostics;

    class SynchronizationContextWorkflowSchedulerService : DefaultWorkflowSchedulerService
    {
        SynchronizationContext syncContext;

        public SynchronizationContextWorkflowSchedulerService()
        {
        }

        protected internal override void Cancel(Guid timerId)
        {
            base.Cancel(timerId);
            if (DiagnosticUtility.ShouldTraceInformation)
            {
                string traceText = SR.GetString(SR.TraceCodeSyncContextSchedulerServiceTimerCancelled, timerId);
                TraceUtility.TraceEvent(TraceEventType.Information,
                    TraceCode.SyncContextSchedulerServiceTimerCancelled, traceText,
                    new StringTraceRecord("TimerDetail", traceText),
                    this, null);
            }
        }

        protected internal override void Schedule(WaitCallback callback, Guid workflowInstanceId, DateTime whenUtc, Guid timerId)
        {
            base.Schedule(callback, workflowInstanceId, whenUtc, timerId);
            if (DiagnosticUtility.ShouldTraceInformation)
            {
                string traceText = SR.GetString(SR.TraceCodeSyncContextSchedulerServiceTimerCreated, timerId, workflowInstanceId);
                TraceUtility.TraceEvent(TraceEventType.Information,
                    TraceCode.SyncContextSchedulerServiceTimerCreated, traceText,
                    new StringTraceRecord("TimerDetail", traceText),
                    this, null);
            }
        }

        protected internal override void Schedule(WaitCallback callback, Guid workflowInstanceId)
        {
            if (callback == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("callback");
            }

            WorkflowDispatchContext currentDispatchContext = WorkflowDispatchContext.Current;

            if (currentDispatchContext != null && currentDispatchContext.IsSynchronous)
            {
                callback(workflowInstanceId);
            }
            else
            {
                if (this.syncContext != null)
                {
                    SynchronizationContextPostHelper.Post(this.syncContext, Fx.ThunkCallback(new SendOrPostCallback(callback)), workflowInstanceId);
                }
                else
                {
                    base.Schedule(callback, workflowInstanceId);
                }
            }
        }

        internal void SetSynchronizationContext(SynchronizationContext synchronizationContext)
        {
            this.syncContext = synchronizationContext;
        }

        internal static class SynchronizationContextPostHelper
        {
            static SendOrPostCallback wrapperCallback = 
                Fx.ThunkCallback(new SendOrPostCallback(SynchronizationContextPostHelper.Callback));

            public static void Post(SynchronizationContext synchronizationContext, SendOrPostCallback callback, object state)
            {
                Fx.Assert(synchronizationContext != null, "Null [....] Context");
                Fx.Assert(callback != null, "Null Callback");

                synchronizationContext.OperationStarted();
                synchronizationContext.Post(wrapperCallback, new PostCallbackState(synchronizationContext, callback, state));
            }

            static void Callback(object state)
            {
                if (state == null)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("state");
                }

                PostCallbackState postCallbackState = state as PostCallbackState;
                Fx.Assert(postCallbackState != null, "Invalid state object passed to callback function");
                try
                {
                    postCallbackState.Callback(postCallbackState.State);
                }
                finally
                {
                    postCallbackState.SynchronizationContext.OperationCompleted();
                }
            }

            class PostCallbackState
            {
                SendOrPostCallback callback;
                object callbackState;
                SynchronizationContext synchronizationContext;

                public PostCallbackState(SynchronizationContext synchronizationContext, SendOrPostCallback callback, object callbackState)
                {
                    this.synchronizationContext = synchronizationContext;
                    this.callback = callback;
                    this.callbackState = callbackState;
                }

                public SendOrPostCallback Callback
                {
                    get
                    {
                        return this.callback;
                    }
                }

                public object State
                {
                    get
                    {
                        return this.callbackState;
                    }
                }

                public SynchronizationContext SynchronizationContext
                {
                    get
                    {
                        return this.synchronizationContext;
                    }
                }
            }
        }
    }
}
