private void EmitUsingDefinitionValue(ILGenerator ilGenerator, FluentActionDefinition fluentActionDefinition, FluentActionUsingDefinition usingDefinition, Dictionary <int, int> methodParameterIndices, StateMachineState state, int stateIndex, int handlerInStateIndex)
        {
            if (usingDefinition.IsMethodParameter)
            {
                var usingDefinitionHash  = usingDefinition.GetHashCode();
                var methodParameterIndex = methodParameterIndices[usingDefinitionHash];

                ilGenerator.Emit(OpCodes.Ldarg_0);
                ilGenerator.Emit(OpCodes.Ldfld, MethodParameterFields[methodParameterIndex - 1]);
            }
            else if (usingDefinition.IsControllerProperty)
            {
                ilGenerator.Emit(OpCodes.Ldarg_0);
                ilGenerator.Emit(OpCodes.Ldfld, ParentField);
                ilGenerator.Emit(OpCodes.Callvirt,
                                 typeof(Controller).GetProperty(usingDefinition.ControllerPropertyName).GetGetMethod());
            }
            else if (usingDefinition is FluentActionUsingPropertyDefinition)
            {
                var propertyName = ((FluentActionUsingPropertyDefinition)usingDefinition).PropertyName;
                var parentType   = fluentActionDefinition.ParentType ?? typeof(Controller);
                var property     = parentType.GetProperty(propertyName);
                if (property == null)
                {
                    throw new Exception($"Could not find property {propertyName} on type {parentType.FullName}.");
                }

                var propertyGetMethod = property.GetGetMethod();
                if (propertyGetMethod == null)
                {
                    throw new Exception($"Missing public get method on property {propertyName} on type {parentType.FullName}.");
                }

                ilGenerator.Emit(OpCodes.Ldarg_0);
                ilGenerator.Emit(OpCodes.Ldfld, ParentField);
                ilGenerator.Emit(OpCodes.Callvirt, propertyGetMethod);
            }
            else if (usingDefinition is FluentActionUsingParentDefinition)
            {
                ilGenerator.Emit(OpCodes.Ldarg_0);
                ilGenerator.Emit(OpCodes.Ldfld, ParentField);
            }
            else if (usingDefinition is FluentActionUsingResultDefinition)
            {
                FieldBuilder resultFieldToLoad;
                if (handlerInStateIndex > 0)
                {
                    resultFieldToLoad = state.Handlers[handlerInStateIndex - 1].ResultField;
                }
                else
                {
                    resultFieldToLoad = States[stateIndex - 1].ResultField;
                }

                ilGenerator.Emit(OpCodes.Ldarg_0);
                ilGenerator.Emit(OpCodes.Ldfld, resultFieldToLoad);

                if (resultFieldToLoad.FieldType.IsValueType)
                {
                    ilGenerator.Emit(OpCodes.Box, resultFieldToLoad.FieldType);
                }
            }
            else
            {
                throw new Exception($"Got unknown using definition: {usingDefinition.GetType()}");
            }
        }
        private void DefineStates(FluentActionDefinition fluentActionDefinition)
        {
            var handlersPerState = fluentActionDefinition.Handlers
                                   .Divide(handler => handler.Async)
                                   .Select(handlers => handlers.ToArray())
                                   .ToArray();

            States = new StateMachineState[handlersPerState.Length];

            for (var stateIndex = 0; stateIndex < handlersPerState.Length; stateIndex++)
            {
                var handlersInState = handlersPerState[stateIndex];
                var state           = new StateMachineState
                {
                    Handlers = new StateMachineStateHandler[handlersInState.Length]
                };

                for (var handlerIndex = 0; handlerIndex < handlersInState.Length; handlerIndex++)
                {
                    var handlerDefinition = handlersInState[handlerIndex];

                    state.Handlers[handlerIndex] = new StateMachineStateHandler
                    {
                        Definition = handlerDefinition
                    };

                    if (handlerDefinition.Type == FluentActionHandlerType.Func)
                    {
                        state.Handlers[handlerIndex].DelegateField = Type.DefineField(
                            $"State{stateIndex}Handler{handlerIndex}Delegate",
                            BuilderHelper.GetDelegateType(handlerDefinition),
                            FieldAttributes.Public);

                        state.Handlers[handlerIndex].ResultField = Type.DefineField(
                            $"State{stateIndex}Handler{handlerIndex}Result",
                            handlerDefinition.ReturnType,
                            FieldAttributes.Public);
                    }
                    else if (handlerDefinition.Type == FluentActionHandlerType.Action)
                    {
                        state.Handlers[handlerIndex].DelegateField = Type.DefineField(
                            $"State{stateIndex}Handler{handlerIndex}Delegate",
                            BuilderHelper.GetDelegateType(handlerDefinition),
                            FieldAttributes.Public);
                    }
                    else if (
                        handlerDefinition.Type == FluentActionHandlerType.View ||
                        handlerDefinition.Type == FluentActionHandlerType.PartialView ||
                        handlerDefinition.Type == FluentActionHandlerType.ViewComponent)
                    {
                        state.Handlers[handlerIndex].ResultField = Type.DefineField(
                            $"State{stateIndex}Handler{handlerIndex}Result",
                            handlerDefinition.ReturnType,
                            FieldAttributes.Public);
                    }
                }

                var lastHandlerInState = handlersInState.Last();
                if (lastHandlerInState.Type == FluentActionHandlerType.Func)
                {
                    state.ResultType = lastHandlerInState.ReturnType;

                    state.ResultField = Type.DefineField(
                        $"State{stateIndex}ReturnType",
                        state.ResultType,
                        FieldAttributes.Public);

                    state.TaskAwaiterType = typeof(TaskAwaiter <>)
                                            .MakeGenericType(lastHandlerInState.ReturnType);

                    state.TaskAwaiterField = Type.DefineField(
                        $"State{stateIndex}Awaiter",
                        state.TaskAwaiterType,
                        FieldAttributes.Public);

                    state.WaitingField = Type.DefineField(
                        $"State{stateIndex}WaitingFlag",
                        typeof(bool),
                        FieldAttributes.Public);
                }
                else if (lastHandlerInState.Type == FluentActionHandlerType.Action)
                {
                    state.ResultType = lastHandlerInState.ReturnType;

                    // state.ResultField is not used since an Action returns void

                    state.TaskAwaiterType = typeof(TaskAwaiter);

                    state.TaskAwaiterField = Type.DefineField(
                        $"State{stateIndex}Awaiter",
                        state.TaskAwaiterType,
                        FieldAttributes.Public);

                    state.WaitingField = Type.DefineField(
                        $"State{stateIndex}WaitingFlag",
                        typeof(bool),
                        FieldAttributes.Public);
                }
                else if (lastHandlerInState.Type == FluentActionHandlerType.View ||
                         lastHandlerInState.Type == FluentActionHandlerType.PartialView ||
                         lastHandlerInState.Type == FluentActionHandlerType.ViewComponent)
                {
                    state.ResultType = lastHandlerInState.ReturnType;

                    state.ResultField = Type.DefineField(
                        $"State{stateIndex}ReturnType",
                        state.ResultType,
                        FieldAttributes.Public);
                }

                States[stateIndex] = state;
            }
        }