/// <summary>
 /// Initializes a new instance of the <see cref="WorkflowResult"/> class.
 /// </summary>
 /// <param name="exitState">The exit state.</param>
 /// <param name="exitAssignee">The exit assignee.</param>
 /// <param name="exitAssigneeType">The exit assignee type.</param>
 public WorkflowResult(ItemState exitState, string exitAssignee = null, ComparisonType? exitAssigneeType = null)
 {
     this.ExitState = exitState;
     this.ExitAssignee = exitAssignee;
     this.ExitAssigneeType = exitAssigneeType;
 }
        /// <summary>
        /// Find all eligible applications and perform system transitions.
        /// </summary>
        /// <param name="session">A secure session.</param>
        public void DoWork(SecureSession session)
        {
            List<SystemTransitionWorkflowStateList> stateTransitionList = this.GetStateTransitions(session);

            foreach (var stateList in stateTransitionList)
            {
                foreach (WorkflowState state in stateList)
                {
                    UnitOfWorkList<ApplicationWorkItem> work = this.FindWork(session, stateList.FormId, state);
                    foreach (UnitOfWork<ApplicationWorkItem> workItem in work)
                    {
                        ApplicationWorkflowItem workflowItem = this.GetWorkflowItem(session, workItem);

                        if (!string.IsNullOrWhiteSpace(workItem.Item.EmailAddress))
                        {
                            workflowItem.UserEmail = workItem.Item.EmailAddress;
                            workflowItem.UserId = workItem.Item.UserId;
                        }
                        else
                        {
                            workflowItem.UserId = session.AuthenticatedUser.Id;
                        }

                        ItemState oldState = new ItemState(workItem.Item.Application.WorkflowState);
                        WorkflowResult result = this.workflowService.Trigger(workflowItem, workItem.Item.Transition, oldState);

                        /*
                        if (workItem.Item.Transition.Trigger == TriggerEvent.EmailLink)
                        {
                            this.MarkWorkflowActionProcessed();
                        }
                        */

                        if (result == null || result.ExitState.Name == oldState.Name)
                        {
                            continue;
                        }

                        this.formServiceGateway.SubmitApplication(session, workItem.Item.Application, result.ExitState.Name, result.ExitAssignee, result.ExitAssigneeType);
                        this.workflowService.LogStateChange(workflowItem, oldState, result.ExitState);
                    }
                }

                /*
                // When paginating...
                work = this.FindWork(session, stateList.FormId, state);
                while (work.Count > 0)
                {

                    // Get more work if available
                    work = this.FindWork(session, stateList.FormId, state);
                }
                */
            }
        }
        /// <summary>
        /// Performs workflow on behalf of the submit.
        /// </summary>
        /// <param name="secureSession">The session data.</param>
        /// <param name="application">The application to perform workflow for.</param>
        /// <param name="existingApplicationData">The existing application data.</param>
        /// <param name="product">The product definition.</param>
        /// <param name="scopedPageList">The list of pages required to process the application in its current state.</param>
        /// <param name="currentItemState">The current workflow state.</param>
        /// <param name="workflowState">The new / requested workflow state.</param>
        /// <param name="assignee">The assignee value from the workflow transition.</param>
        /// <param name="assigneeType">The assignee type from the workflow transition.</param>
        /// <param name="newState">After a successful transition, will be the new workflow state, otherwise <see langword="null"/>.</param>
        /// <returns>After a successful transition, the workflow item, otherwise <see langword="null"/>.</returns>
        private ApplicationWorkflowItem PerformWorkflow(SecureSession secureSession, Application application, ApplicationData existingApplicationData, ProductDefinition product, PageList scopedPageList, ItemState currentItemState, string workflowState, string assignee, ComparisonType? assigneeType, out ItemState newState)
        {
            newState = currentItemState;

            WorkflowConfigurationContainer workflowContainer = this.workflowService.GetWorkflowConfiguration(WorkflowTargetType.FormApplication, application.FormId, product.Version);
            WorkflowState state = workflowContainer.Configuration.States.FirstOrDefault(s => s.Name == currentItemState.Name);

            WorkflowTransition transition = (state == null) ?
                                                       null :
                                                            string.IsNullOrWhiteSpace(workflowState) ?
                                                            null :
                                                                state.Transitions.FirstOrDefault(t => t.Trigger != TriggerEvent.Submit && t.ExitStateName == workflowState);

            Organisation entitleTo;

            ApplicationWorkflowItem workflowItem = new ApplicationWorkflowItem(application, existingApplicationData, scopedPageList.AllControls)
            {
                FormVersion = product.Version
            };

            // Do non-submit transition. Only Service accounts can perform this operation.
            if (transition != null && secureSession.AuthenticatedUser != null && secureSession.AuthenticatedUser.AccountType == AccountType.Service)
            {
                User assignTo = this.DetermineAssignee(assignee, assigneeType, application);
                application.WorkflowState = transition.ExitStateName;
                application.StateTransitionDate = DateTime.Now;
                application.AssignedTo = (assignTo != null) ? assignTo.Id : null;
                application.AssignedToDisplayName = (assignTo != null) ? assignTo.DisplayName : null;

                entitleTo = this.DetermineOrganisation(transition.ExitOrganisation, application);
                application.OrganisationId = (entitleTo != null) ? entitleTo.Id : application.OrganisationId;
                application.OrganisationName = (entitleTo != null) ? entitleTo.Name : application.OrganisationName;

                return workflowItem;
            }

            // Do submit transition.
            WorkflowResult result = this.workflowService.Trigger(workflowItem, new WorkflowTransition { Trigger = TriggerEvent.Submit }, currentItemState);

            // If result = null, there were NO valid transitions, so we leave the application essentially as is, but unassign it.
            if (result == null)
            {
                application.AssignedTo = null;
                application.AssignedToDisplayName = null;
            }

            if (result != null)
            {
                newState = result.ExitState;
                if (newState != null && string.Compare(newState.Name, application.WorkflowState, StringComparison.OrdinalIgnoreCase) != 0)
                {
                    application.StateTransitionDate = DateTime.Now;
                }

                application.WorkflowState = (newState == null) ? null : newState.Name;

                User user = this.DetermineAssignee(result.ExitAssignee, result.ExitAssigneeType, application);
                application.AssignedTo = (user != null) ? user.Id : null;
                application.AssignedToDisplayName = (user != null) ? user.DisplayName : null;

                entitleTo = this.DetermineOrganisation(result.ExitOrganisation, application);
                application.OrganisationId = (entitleTo != null) ? entitleTo.Id : application.OrganisationId;
                application.OrganisationName = (entitleTo != null) ? entitleTo.Name : application.OrganisationName;
            }

            return workflowItem;
        }
        /// <summary>
        /// Saves an application following successful validation and workflow processing.
        /// </summary>
        /// <param name="sessionData">The session data.</param>
        /// <param name="application">The <see cref="Application" /> to save.</param>
        /// <param name="workflowState">For a manual transition, the desired state to transition to.</param>
        /// <param name="assignee">The assignee value from the workflow transition.</param>
        /// <param name="assigneeType">The assignee type from the workflow transition.</param>
        /// <param name="submitPageId">The id of the page that triggered the form submission.</param>
        /// <returns>The saved <see cref="Application" />.</returns>
        public Application SubmitApplication(SessionData sessionData, Application application, string workflowState = null, string assignee = null, ComparisonType? assigneeType = null, int? submitPageId = null)
        {
            User user = this.userManager.GetUserById(sessionData.UserId);

            SecureSession secureSession = new SecureSession(sessionData.DeserializationSource, sessionData.SessionId, user);
            ProductDefinition product = this.UpdateApplicationMetadata(secureSession, application);
            application.Draft = false;

            submitPageId = submitPageId ?? product.FormDefinition.Pages[0].PageId;
            PageList previousPages = product.FormDefinition.Pages.GetRange(submitPageId.Value);

            var rolesList = this.DataAccess.GetRoleList();

            AccessLevel applicationAccess = this.entitlementProvider.GetApplicationAccess(secureSession, application, rolesList, product.Version);
            if (applicationAccess != AccessLevel.Write)
            {
                throw new SecurityException(ExceptionMessages.UnauthorisedApplicationSave);
            }

            Application existingApplication = application.IsNew ? null : this.GetApplication(application.ApplicationId);
            ApplicationData existingApplicationData = existingApplication == null ? null : existingApplication.ApplicationData;

            if (existingApplication != null)
            {
                application.ApplicationIdDisplay = existingApplication.ApplicationIdDisplay;
            }

            List<PageAccess> pagesAccess = this.entitlementProvider.GetPagesAccess(secureSession, application, previousPages, rolesList, product.Version);

            // For validation, calculations and workflow execution we need only the controls that resides in previous pages and are accessible under the current application state
            List<Page> previousAccessiblePages = previousPages.Where(p => pagesAccess.Any(e => e.Id == p.PageId && e.AccessLevel >= AccessLevel.Read)).ToList();
            PageList scopedPages = new PageList(previousAccessiblePages);
            ControlList scopedControls = scopedPages.AllControls;

            List<ControlAccess> controlsAccess = this.entitlementProvider.GetControlsAccess(secureSession, application, scopedControls, rolesList, product.Version);

            if (user == null || user.AccountType != AccountType.Service)
            {
                ValidationResults validationResults = this.ValidateApplication(application, scopedPages, scopedControls, controlsAccess);
                if (!validationResults.IsValid)
                {
                    throw new ValidationException(validationResults);
                }
            }

            this.VerifyApplicationDataIntegrity(application, existingApplicationData, scopedControls, controlsAccess);
            this.RunCalculations(application, scopedPages.AllControls);

            // Workflow
            ItemState currentItemState = new ItemState(application.WorkflowState);
            ItemState newState;
            ApplicationWorkflowItem workflowItem = this.PerformWorkflow(secureSession, application, existingApplicationData, product, scopedPages, currentItemState, workflowState, assignee, assigneeType, out newState);

            // Save application.
            Application savedApplication = this.DataAccess.SaveApplication(application);
            this.LogWorkflow(secureSession, savedApplication, application.IsNew, workflowItem, currentItemState, newState);
            return savedApplication;
        }
        /// <summary>
        /// Logs workflow state changes.
        /// </summary>
        /// <param name="secureSession">The session data.</param>
        /// <param name="application">The saved application.</param>
        /// <param name="isNew">A value indicating whether the application is new.</param>
        /// <param name="workflowItem">The workflow item to log.</param>
        /// <param name="previousState">The previous state of <paramref name="application"/>.</param>
        /// <param name="newState">The new state of <paramref name="application"/>.</param>
        private void LogWorkflow(SecureSession secureSession, Application application, bool isNew, ApplicationWorkflowItem workflowItem, ItemState previousState, ItemState newState)
        {
            if (workflowItem == null && !isNew)
            {
                return;
            }

            workflowItem = workflowItem ?? new ApplicationWorkflowItem
            {
                FormId = application.FormId,
                UserId = secureSession.AuthenticatedUser != null ? secureSession.AuthenticatedUser.Id : null
            };

            workflowItem.ApplicationId = application.ApplicationId;
            workflowItem.VersionNumber = application.VersionNumber;
            workflowItem.PostedData = application.ApplicationData;

            if (isNew && newState != null && previousState.Name != newState.Name)
            {
                // Special case to log from blank to the first state when we are moving straight into the next state.
                this.workflowService.LogStateChange(workflowItem, new ItemState(null), previousState);
            }

            if (isNew && (newState == null || previousState.Name == newState.Name))
            {
                newState = previousState;
                previousState = new ItemState(null);
            }

            this.workflowService.LogStateChange(workflowItem, previousState, newState);
        }