using Valg.EVA.Skanning.Contracts.CommonEnums;
using Valg.EVA.Skanning.ServicesModel.ServiceContracts.Verification.Read;
using Valg.EVA.Skanning.ServicesModel.ServiceContracts.Verification.Write;
using Valg.EVA.Skanning.UserInterfaceElements;
using Valg.EVA.Skanning.UserInterfaceElements.ApplicationEvents;
using Valg.EVA.Skanning.UserInterfaceElements.BallotPaperImage;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Input;
using Prism.Commands;
using Valg.EVA.Skanning.ContainerExtensions;
using Valg.EVA.Skanning.UserInterfaceElements.ApplicationTermination;
using Valg.EVA.Skanning.UserInterfaceElements.Commands;
using Valg.EVA.Skanning.Verify.Startup.BoxIsLocked;
using Valg.EVA.Skanning.Verify.Startup.VerifyBallotPaper.BallotPaperStatus;
using Valg.EVA.Skanning.Verify.Startup.VerifyBallotPaper.VerifiableViewModelsCollection;
using Valg.EVA.Skanning.Verify.Startup.VerifyBallotPaper.Messaging.Commands;
using Valg.EVA.Skanning.Verify.Startup.VerifyBallotPaper.Messaging.Events;
using Valg.EVA.Skanning.Verify.Startup.VerifyBallotPaper.ReadModel;
using Valg.EVA.Skanning.Verify.Startup.VerifyBallotPaper.WriteModel;

namespace Valg.EVA.Skanning.Verify.Startup.VerifyBallotPaper
{
    public class NavigateBallotsViewModel : ViewModelBase, INavigateBallotsViewModel
    {
        private readonly DelegateCommand saveAndNextCommand;
        private readonly DelegateCommand previousCommand;
        private readonly IVerificationProcessErrorsAndWarnings verificationProcessErrorsAndWarnings;
        private readonly IBallotCompleteViewModel ballotCompleteViewModel;
        private readonly IWaitCursorProvider waitCursorProvider;
        private readonly IWriteModelHub writeModelHub;
        private readonly IReadModelHub readModelHub;
        private readonly ITypedFactory typedFactory;

        private IVerifyBallotPaperViewModel currentBallotPaperForVerify;

        public CurrentInfo CurrentInfo { get; }

        public NavigateBallotsViewModel(
            IVerificationProcessErrorsAndWarnings verificationProcessErrorsAndWarnings,
            IBallotCompleteViewModel ballotCompleteViewModel,
            IWriteModelHub writeModelHub,
            IWaitCursorProvider waitCursorProvider,
            IReadModelHub readModelHub,
            ITypedFactory typedFactory
            )
        {
            this.verificationProcessErrorsAndWarnings = verificationProcessErrorsAndWarnings;
            this.ballotCompleteViewModel = ballotCompleteViewModel;
            this.writeModelHub = writeModelHub;
            this.waitCursorProvider = waitCursorProvider;
            this.readModelHub = readModelHub;
            this.typedFactory = typedFactory;

            CurrentInfo = new CurrentInfo();

            saveAndNextCommand = GuardedCommand.Create(() => SaveAndGotoNextBallotPaperForVerification());
            previousCommand = GuardedCommand.Create(() => SaveAndGotoPreviousBallotPaperForVerification());

            PropertyChanged += OwnPropertyChanged;
        }

        protected override void SubscribeToViewModelSpecificEvents()
        {
            SubscribeToEvent<ApplicationTerminatingEvent, ApplicationTerminatingEventArgs>(args =>
            {
                if (CurrentInfo.IsAtEndOfContinuousStreamOfUnverifiedBallotPapers())
                {
                    RevokeSeenStatusForCurrentBallotPaper();
                }
            });

            SubscribeToEvent<BallotAbandonedEvent, EventArgs>(args =>
            {
                if (CurrentInfo.IsAtEndOfContinuousStreamOfUnverifiedBallotPapers())
                {
                    RevokeSeenStatusForCurrentBallotPaper();
                }
            });
            
            SubscribeToEvent<RejectionReasonChangedEvent, RejectionReasonChangedEventArgs>(args =>
            {
                PublishBallotStatusUpdatedEvent();
            });

            SubscribeToEvent<BallotVerificationCompletedEvent, BallotVerificationCompletedEventArgs>(args =>
            {
                if (CurrentBallotPaperForVerify.VerifiableViewModelsCollectionVisitor.NoMoreViewModelsToVerifyOrRejected &&
                    !CurrentBallotPaperForVerify.HasBallotCompleteTipBeenShown &&
                    !CurrentBallotPaperForVerify.BallotPaper.BallotNo.IsBlank)
                {
                    CurrentBallotPaperForVerify.HasBallotCompleteTipBeenShown = true;
                    ballotCompleteViewModel.ShowTipIfNotHiddenByUser();
                }
            });
        }

        private void OwnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
        {
            PublishBallotStatusUpdatedEvent();
        }

        private void PublishBallotStatusUpdatedEvent()
        {
            PublishEvent<BallotStatusUpdatedEvent, BallotStatus>(ResolveStatus());
        }

        public IVerifyBallotPaperViewModel CurrentBallotPaperForVerify
        {
            get { return currentBallotPaperForVerify; }
            set
            {
                if (currentBallotPaperForVerify != null)
                {
                    currentBallotPaperForVerify.PropertyChanged -= NotifyPropertyChangedWhenCurrentBallotPaperChanges;
                    currentBallotPaperForVerify.VerifiableViewModelsCollectionVisitor.PropertyChanged -= NotifyPropertyChangedWhenCurrentBallotPaperChanges;
                    currentBallotPaperForVerify.VerifiableViewModelsCollectionVisitor.Dispose();
                    currentBallotPaperForVerify.Dispose();
                }

                currentBallotPaperForVerify = value;

                if (currentBallotPaperForVerify != null)
                {
                    currentBallotPaperForVerify.PropertyChanged += NotifyPropertyChangedWhenCurrentBallotPaperChanges;
                    currentBallotPaperForVerify.VerifiableViewModelsCollectionVisitor.PropertyChanged += NotifyPropertyChangedWhenCurrentBallotPaperChanges;
                    UpdateBallotPaperImage();
                }

                OnPropertyChanged();
            }
        }

        public ICommand PreviousCommand => previousCommand;

        public ICommand SaveAndNextCommand => saveAndNextCommand;

        public bool IsPreviousButtonEnabled => CurrentInfo.CanAdvanceToPrevious;

        public bool IsNextButtonEnabled
        {
            get
            {
                if (CurrentBallotPaperForVerify != null)
                {
                    if (CurrentBallotPaperForVerify.VerifiableViewModelsCollectionVisitor.NoMoreViewModelsToVerifyOrRejected)
                    {
                        return true;
                    }

                    var latestModification = writeModelHub.GetLatestRejectionReasonModification();
                    if (latestModification != null && RejectionReasonsCollections.AllActualRejectionReasons.Contains(latestModification.NewValue))
                    {
                        return true;
                    }
                }

                return false;
            }
        }

        public void Preload(int[] ballotPaperIds)
        {
            CurrentInfo.Preload(ballotPaperIds);
        }

        public void Reset(int boxCountingId)
        {
            CurrentInfo.Reset(boxCountingId);

            CurrentBallotPaperForVerify = null;
        }

        public void AdvanceToNextBallotPaper()
        {
            var advanceRules = CurrentInfo.DetermineForwardAdvanceRules();

            var boxVerificationComplete = advanceRules.BoxVerificationIsComplete;
            var returnToStartPage = false;
            if (advanceRules.CanAdvance)
            {
                if (advanceRules.GetNewBallotPaper)
                {
                    var getNextResult = readModelHub.GetNextBallotPaper(CurrentInfo.CurrentBoxCountingId);

                    if (getNextResult.Failure)
                    {
                        verificationProcessErrorsAndWarnings.ShowErrorWhenAttemptingToLoadABallotPaper(getNextResult.Exception);
                        returnToStartPage = CurrentInfo.CurrentBallotPaperId == null;

                        if (!returnToStartPage)
                        {
                            SetCurrentBallotPaperFromKnownBallotPaperId(CurrentInfo.CurrentBallotPaperId);
                        }
                    }
                    else
                    {
                        if (getNextResult.NoBallotPaperWasFound.HasValue && getNextResult.NoBallotPaperWasFound.Value)
                        {
                            boxVerificationComplete = true;
                        }
                        else
                        {
                            SetCurrentBallotPaperToANewBallotPaper(getNextResult);

                            ShowWarnings(getNextResult);
                        }
                    }
                }
                else
                {
                    SetCurrentBallotPaperFromKnownBallotPaperId(advanceRules.BallotPaperIdToAdvanceTo);
                }
            }

            if (returnToStartPage)
            {
                GoToStartVerifyPage();
            }
            else if (boxVerificationComplete)
            {
                PublishEvent<BoxVerificationCompletedEvent, EventArgs>(EventArgs.Empty);
            }

            OnPropertyChanged(null);
        }

        private void ShowWarnings(GetNextBallotPaperToVerifyResult getNextResult)
        {
            ShowUnknownBallotInformation(getNextResult);
        }

        private void ShowUnknownBallotInformation(GetNextBallotPaperToVerifyResult getNextResult)
        {
            if (getNextResult.BallotPaperToVerify.BallotPaperType == BallotPaperType.Unknown)
            {
                verificationProcessErrorsAndWarnings.ShowBallotIsUnknownInformation();

                SetFocusAfterPopUpCloses();
            }
        }

        private void SetFocusAfterPopUpCloses()
        {
            if (!currentBallotPaperForVerify.VerifiableViewModelsCollectionVisitor.HasViewModelsWithUnverifiedContent)
            {
                currentBallotPaperForVerify.BallotNumberViewModel.BringToFocus();
            }
            else
            {
                currentBallotPaperForVerify.VerifiableViewModelsCollectionVisitor.GotoNextVerifiableViewModel(false);
            }
        }

        public void AdvanceToPreviousBallotPaper()
        {
            if (CurrentInfo.CanAdvanceToPrevious)
            {
                SetCurrentBallotPaperFromKnownBallotPaperId(CurrentInfo.CurrentBallotPaperId.Previous);
            }

            OnPropertyChanged(null);
        }

        public void AdvanceToLastVerifiedBallotPaper()
        {
            if (CurrentInfo.HasPreviouslyLoadedBallotpapers())
            {
                SetCurrentBallotPaperFromKnownBallotPaperId(CurrentInfo.CurrentBallotPaperId);
                PublishEvent<VerifyOnBoxIsStartedEvent, bool>(true);
            }
        }

        private void NotifyPropertyChangedWhenCurrentBallotPaperChanges(object sender, PropertyChangedEventArgs e)
        {
            OnPropertyChanged(null);
        }

        private BallotStatus ResolveStatus()
        {
            if (CurrentBallotPaperForVerify != null)
            {
                if (writeModelHub.IsRejected)
                    return BallotStatus.SuggestedRejected;

                if (CurrentBallotPaperForVerify.VerifiableViewModelsCollectionVisitor.NoMoreViewModelsToVerifyOrRejected)
                    return BallotStatus.Verified;
            }

            return BallotStatus.Started;
        }

        private void UpdateBallotPaperImage()
        {
            PublishEvent<BallotPaperImageUpdatedEvent, IBallotPaperImageViewModel>(CurrentBallotPaperForVerify.Preview.BallotPaperImageViewModel);
        }

        private void SetCurrentBallotPaperToANewBallotPaper(GetNextBallotPaperToVerifyResult getNextResult)
        {
            var ballotPaperVerificationViewModel = typedFactory.Create<IVerifyBallotPaperViewModel>();
            ballotPaperVerificationViewModel.Initialize(getNextResult.BallotPaperToVerify, 
                getNextResult.Counting, getNextResult.TotalToVerification);

            CurrentBallotPaperForVerify = ballotPaperVerificationViewModel;
            CurrentInfo.PreviouslyVisitedBallotPapers.AddLast(getNextResult.BallotPaperToVerify.Id);
            CurrentInfo.CurrentBallotPaperId = CurrentInfo.PreviouslyVisitedBallotPapers.Last;

            CurrentBallotPaperForVerify.VerifiableViewModelsCollectionVisitor.GotoNextVerifiableViewModel(false);
        }

        private void SetCurrentBallotPaperFromKnownBallotPaperId(LinkedListNode<int> ballotPaperIdToAdvanceTo)
        {
            var getResult = readModelHub.GetBallotPaper(ballotPaperIdToAdvanceTo.Value);

            CurrentInfo.CurrentBallotPaperId = ballotPaperIdToAdvanceTo;

            var ballotPaperVerificationViewModel = typedFactory.Create<IVerifyBallotPaperViewModel>();
            ballotPaperVerificationViewModel.Initialize(getResult.BallotPaper, 
                getResult.Counting, getResult.TotalToVerification);

            CurrentBallotPaperForVerify = ballotPaperVerificationViewModel;
            CurrentBallotPaperForVerify.PageXOfYViewModel.ChangePageIndexToCurrentRejectionIfSet();
        }

        private void GoToStartVerifyPage()
        {
            PublishEvent<MainWindowRequestedEvent, EventArgs>(EventArgs.Empty);
        }

        private void SaveAndGotoNextBallotPaperForVerification()
        {
            SaveBallotPaperVerificationsResult result;
            using (waitCursorProvider.GetWaitCursor())
            {
                result = writeModelHub.SaveBallotPaperForVerification(CurrentBallotPaperForVerify.BallotPaper);
            }

            if (result.Success)
            {
                using (waitCursorProvider.GetWaitCursor())
                {
                    AdvanceToNextBallotPaper();
                }
            }
            else
            {
                if (result.SaveBallotPaperVerificationsOperationResult == SaveBallotPaperVerificationsOperationResult.Incomplete)
                {
                    verificationProcessErrorsAndWarnings.ShowCannotSaveBallotPaperVerificationsWhenIncomplete(new DelegateCommand(UndoChangesAndGoToNextBallotPaper));
                }
                else
                {
                    verificationProcessErrorsAndWarnings.ShowCannotChangeInformation(result.SaveBallotPaperVerificationsOperationResult);
                    GoToStartVerifyPage();
                }
            }
        }

        private void UndoChangesAndGoToNextBallotPaper()
        {
            RevokeSeenStatusForCurrentBallotPaper();
            AdvanceToNextBallotPaper();
        }

        private void SaveAndGotoPreviousBallotPaperForVerification()
        {
            using (waitCursorProvider.GetWaitCursor())
            {
                if (CurrentInfo.DataWasPreloaded)
                {
                    var result = writeModelHub.SaveBallotPaperForVerification(CurrentBallotPaperForVerify.BallotPaper);

                    if (result.Success)
                    {
                        using (waitCursorProvider.GetWaitCursor())
                        {
                            AdvanceToPreviousBallotPaper();
                        }
                    }
                    else
                    {
                        if (result.SaveBallotPaperVerificationsOperationResult == SaveBallotPaperVerificationsOperationResult.Incomplete)
                        {
                            verificationProcessErrorsAndWarnings.ShowCannotSaveBallotPaperVerificationsWhenIncomplete(new DelegateCommand(UndoChangesAndGoToPreviousBallotPaper));
                        }
                        else
                        {
                            verificationProcessErrorsAndWarnings.ShowCannotChangeInformation(result.SaveBallotPaperVerificationsOperationResult);
                            GoToStartVerifyPage();
                        }
                    }
                }
                else 
                {
                    LinkedListNode<int> paperToRemoveFromVisitedCollection = null;

                    var wasRevoked = RevokeSeenStatusForCurrentBallotPaper();

                    if (wasRevoked)
                    {
                        paperToRemoveFromVisitedCollection = CurrentInfo.CurrentBallotPaperId;
                    }

                    using (waitCursorProvider.GetWaitCursor())
                    {
                        AdvanceToPreviousBallotPaper();
                    }

                    if (paperToRemoveFromVisitedCollection != null)
                    {
                        CurrentInfo.PreviouslyVisitedBallotPapers.Remove(paperToRemoveFromVisitedCollection);
                    }
                }
            }
        }

        private void UndoChangesAndGoToPreviousBallotPaper()
        {
            RevokeSeenStatusForCurrentBallotPaper();
            AdvanceToPreviousBallotPaper();
        }

        private bool RevokeSeenStatusForCurrentBallotPaper()
        {
            return writeModelHub.RevokeBallotPaperSeenStatus(CurrentBallotPaperForVerify.BallotPaper.BallotPaperConcurrencyToken,
                CurrentBallotPaperForVerify.BallotPaper.Id);
        }
    }
}
