using System.Collections.Generic;
using System.Collections.ObjectModel;
using Valg.EVA.Skanning.Entity.ReadModel;
using Valg.EVA.Skanning.Repositories.BallotPaper;
using Valg.EVA.Skanning.Repositories.Counting;
using Valg.EVA.Skanning.ServicesModel.Internal.BallotNumber;
using Valg.EVA.Skanning.ServicesModel.ElectionConfig.DataProvider;
using Valg.EVA.Skanning.ServicesModel.ElectionConfig.Model;
using Valg.EVA.Skanning.ServicesModel.Internal.Interpreter.Modifications;

namespace Valg.EVA.Skanning.ServicesModel.Internal.Interpreter
{
    public class BoxContentsInterpreter : IBoxContentsInterpreter
    {
        private readonly IBallotPaperRepository ballotPaperRepository;
        private readonly BallotNumberInterpreter ballotNumberInterpreter;
        private readonly BallotIdInterpreter ballotIdInterpreter;
        private readonly WriteInInterpreter writeInInterpreter;
        private readonly ICountingRepository countingRepository;
        private readonly IElectionConfigDataProvider electionConfigDataProvider;
        private readonly ICandidateListDataProvider candidateListDataProvider;
        private readonly CountingUniqueIdCreator countingUniqueIdCreator;
        private readonly PersonalVotingAdjuster personalVotingAdjuster;
        private readonly CountingOnTopLevelInterpreter countingOnTopLevelInterpreter;
        private readonly GeneralBallotInterpreter generalBallotInterpreter;
        private readonly MultipleStampsInterpreter multipleStampsInterpreter;
        private readonly RenumberingInterpreter renumberingInterpreter;
        private readonly BlankBallotInterpreter blankBallotInterpreter;
        private readonly BallotHeaderInterpreter ballotHeaderInterpreter;

        public BoxContentsInterpreter(
            BallotNumberInterpreter ballotNumberInterpreter,
            BallotIdInterpreter ballotIdInterpreter,
            WriteInInterpreter writeInInterpreter,
            CountingOnTopLevelInterpreter countingOnTopLevelInterpreter,
            GeneralBallotInterpreter generalBallotInterpreter,
            MultipleStampsInterpreter multipleStampsInterpreter,
            RenumberingInterpreter renumberingInterpreter,
            BlankBallotInterpreter blankBallotInterpreter,
            IBallotPaperRepository ballotPaperRepository,
            ICountingRepository countingRepository,
            IElectionConfigDataProvider electionConfigDataProvider,
            ICandidateListDataProvider candidateListDataProvider,
            CountingUniqueIdCreator countingUniqueIdCreator,
            PersonalVotingAdjuster personalVotingAdjuster,
            BallotHeaderInterpreter ballotHeaderInterpreter)
        {
            this.ballotPaperRepository = ballotPaperRepository;
            this.ballotNumberInterpreter = ballotNumberInterpreter;
            this.ballotIdInterpreter = ballotIdInterpreter;
            this.writeInInterpreter = writeInInterpreter;
            this.countingRepository = countingRepository;
            this.electionConfigDataProvider = electionConfigDataProvider;
            this.candidateListDataProvider = candidateListDataProvider;
            this.countingUniqueIdCreator = countingUniqueIdCreator;
            this.personalVotingAdjuster = personalVotingAdjuster;
            this.countingOnTopLevelInterpreter = countingOnTopLevelInterpreter;
            this.generalBallotInterpreter = generalBallotInterpreter;
            this.multipleStampsInterpreter = multipleStampsInterpreter;
            this.renumberingInterpreter = renumberingInterpreter;
            this.blankBallotInterpreter = blankBallotInterpreter;
            this.ballotHeaderInterpreter = ballotHeaderInterpreter;
        }

        public InterpreterModificationBuffer InterpretBoxContents(string barcodeLong, string barcodeShort, int countingIdBoxWasScannedFor, string countingEmlAreaContextId)
        {
            var activeCounting = countingRepository.GetActiveCounting(
                countingUniqueIdCreator.Create(barcodeLong, barcodeShort),
                countingEmlAreaContextId,
                countingIdBoxWasScannedFor);

            var boxCounting = countingRepository.GetBoxCounting(barcodeLong, barcodeShort, activeCounting.Id);
            var ballotPapersInCounting = ballotPaperRepository.GetBallotPapersInBoxCounting(boxCounting.Id);
            var modificationBuffer = new InterpreterModificationBuffer(boxCounting);

            CheckBallotPaperIntegrity(
                boxCounting,
                ballotPapersInCounting,
                modificationBuffer);

            return modificationBuffer;
        }
        
        private void CheckBallotPaperIntegrity(BoxCounting boxCounting, IEnumerable<BallotPaperInBoxCounting> ballotPapers, InterpreterModificationBuffer modificationBuffer)
        {
            foreach (var ballotPaper in ballotPapers)
            {
                var ballotPaperFieldValues = ballotPaperRepository.GetCurrentFieldValues(ballotPaper.BallotPaperId);

                generalBallotInterpreter.CheckIntegrity(ballotPaper, ballotPaperFieldValues, modificationBuffer, boxCounting.BarcodeLong, ballotPaper.BallotPaperType);

                ballotNumberInterpreter.CheckBallotNumberIntegrity(ballotPaper, ballotPaperFieldValues, modificationBuffer, boxCounting.BarcodeLong);
                ballotHeaderInterpreter.CheckIntegrity(ballotPaper, ballotPaperFieldValues, modificationBuffer, boxCounting.BarcodeLong);

                multipleStampsInterpreter.CheckIntegrity(ballotPaperFieldValues, modificationBuffer, boxCounting.BarcodeLong, ballotPaper.BallotPaperType);

                writeInInterpreter.CheckWriteInIntegrity(ballotPaperFieldValues, modificationBuffer, boxCounting.BarcodeLong);

                ballotIdInterpreter.EnsureBallotIdIsNotValidated(ballotPaperFieldValues, modificationBuffer);

                personalVotingAdjuster.AdjustHandlingOfPersonalVotingToContest(ballotPaperFieldValues, modificationBuffer, boxCounting.BarcodeLong, boxCounting.CountingType);

                blankBallotInterpreter.CheckIntegrity(ballotPaper, ballotPaperFieldValues, modificationBuffer, boxCounting.BarcodeLong, ballotPaper.BallotPaperType, boxCounting.CountingType);

                countingOnTopLevelInterpreter.AdjustValidationRulesIfCountedOnLowerLevel(boxCounting, ballotPaper, ballotPaperFieldValues, modificationBuffer);

                renumberingInterpreter.CheckIntegrity(ballotPaper, ballotPaperFieldValues, modificationBuffer, boxCounting.CountingType);

                AdjustBallotPaperNeedsVerification(ballotPaper, ballotPaperFieldValues, modificationBuffer, boxCounting.BarcodeLong);
            }
        }

        private void AdjustBallotPaperNeedsVerification(BallotPaperInBoxCounting ballotPaper, ReadOnlyCollection<BallotPaperFieldValue> ballotPaperFieldValues, InterpreterModificationBuffer modificationBuffer, string barcodeLong)
        {
            var electionEventId = barcodeLong.GetElectionEventIdFromBarcodeLong();
            var contestId = barcodeLong.GetContestIdFromBarcodeLong();

            var candidatesOnList = GetNumberOfCandidatesOnList(ballotPaper, ballotPaperFieldValues.GetBallotNumberField(), modificationBuffer, electionEventId, contestId);

            var maxWriteIns = electionConfigDataProvider.GetMaxWriteInForContest(electionEventId, contestId).GetValueOrDefault();

            modificationBuffer.AdjustBallotPaperNeedsVerification(ballotPaper, ballotPaperFieldValues, maxWriteIns, candidatesOnList);
        }

        private int GetNumberOfCandidatesOnList(BallotPaperInBoxCounting ballotPaper, BallotPaperFieldValue ballotNumberField, InterpreterModificationBuffer modificationBuffer, string electionEventId, string contestId)
        {
            if (!ballotNumberField.NeedsVerification &&
                !modificationBuffer.IsBallotPaperMarkedAsIgnoreCandidates(ballotPaper) &&
                !Affiliation.IsProposedShortCodeBlankAffiliation(ballotNumberField.Value.AffiliationShortCode()))
            {
                return candidateListDataProvider.GetAffiliation(electionEventId, contestId, ballotNumberField.Value.AffiliationShortCode()).Candidates.Count;
            }

            return 0;
        }
    }
}
