using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Valg.EVA.Skanning.Contracts;
using Valg.EVA.Skanning.Contracts.CommonEnums;
using Valg.EVA.Skanning.Entity.ReadModel;
using Valg.EVA.Skanning.ServicesModel.Internal.Serializers;

namespace Valg.EVA.Skanning.ServicesModel.Internal.Interpreter.Modifications
{
    public class InterpreterModificationBuffer
    {
        private int highestVerificationIndex;
        private readonly List<BallotPaperFieldValue> modifiedFields;
        private readonly Dictionary<int, BallotPaperValuesInterpreterModification> ballotPapersToUpdate; 

        public InterpreterModificationBuffer(BoxCounting activeBoxCounting)
        {
            ActiveBoxCounting = activeBoxCounting;
            modifiedFields = new List<BallotPaperFieldValue>();
            
            ballotPapersToUpdate = new Dictionary<int, BallotPaperValuesInterpreterModification>();
        }

        public BoxCounting ActiveBoxCounting { get; private set; }

        public ReadOnlyCollection<BallotPaperValuesInterpreterModification> GetBallotPapersToUpdate()
        {
            return ballotPapersToUpdate.Values.ToList().AsReadOnly();
        }

        internal void AddModifiedField(BallotPaperFieldValue field)
        {
            modifiedFields.Add(field);
        }

        public ReadOnlyCollection<BallotPaperFieldValue> GetModifiedFields()
        {
            return modifiedFields.AsReadOnly();
        }

        internal void SetBallotPaperToBeMarkedAsIgnoreCandidates(BallotPaperInBoxCounting ballotPaper)
        {
            GetBallotPaper(ballotPaper.BallotPaperId, ballotPaper.CurrentBallotPaperValuesId).IgnoreCandidates = true;
        }

        public bool IsBallotPaperMarkedAsIgnoreCandidates(BallotPaperInBoxCounting ballotPaper)
        {
            return GetBallotPaper(ballotPaper.BallotPaperId, ballotPaper.CurrentBallotPaperValuesId).IgnoreCandidates;
        }

        internal void SetBallotPaperToBeMarkedAsInvalid(BallotPaperInBoxCounting ballotPaper)
        {
            GetBallotPaper(ballotPaper.BallotPaperId, ballotPaper.CurrentBallotPaperValuesId).IsValid = false;
        }

        internal void SetCorrectionFieldsToNotNeedingVerification(IReadOnlyCollection<BallotPaperFieldValue> fields)
        {
            fields
                .Where(f =>
                    f.IsCandidateField() ||
                    f.IsWriteInField() ||
                    f.IsRenumberField() ||
                    f.IsStrikeoutField())
                .ToList()
                .ForEach(v =>
                {
                    v.NeedsVerification = false;

                    if (v.IsWriteInField())
                    {
                        var writeInData = WriteInDataJsonSerializer.Deserialize(v);
                        writeInData.NeedsVerification = false;
                        v.SerializeWriteInDataToJson(writeInData);
                    }

                    if (!modifiedFields.Contains(v))
                    {
                        AddModifiedField(v);
                    }
                });
        }

        public void AdjustBallotPaperNeedsVerification(BallotPaperInBoxCounting ballotPaperInBoxCounting, IReadOnlyCollection<BallotPaperFieldValue> fields, int maxWriteIns, int maxNumberOfCandidatesOnList)
        {
            var ballotPaperValuesToUpdate = GetBallotPaper(ballotPaperInBoxCounting.BallotPaperId, ballotPaperInBoxCounting.CurrentBallotPaperValuesId);

            var needsVerification = DetermineNeedsVerificationOnEntireBallot(fields, maxWriteIns, maxNumberOfCandidatesOnList, ballotPaperValuesToUpdate);

            if (needsVerification)
            {
                ballotPaperValuesToUpdate.NeedsVerification = true;
                ballotPaperValuesToUpdate.VerificationIndex = GetNextVerificationIndex();
            }
            else
            {
                ballotPaperValuesToUpdate.IsValid = true;
                ballotPaperValuesToUpdate.NeedsVerification = false;
            }
        }

        private bool DetermineNeedsVerificationOnEntireBallot(IReadOnlyCollection<BallotPaperFieldValue> readOnlyFields, int maxWriteIns, int maxNumberOfCandidatesOnList, BallotPaperValuesInterpreterModification ballotPaperValuesInterpreterModification)
        {
            var fields = readOnlyFields.ToList();
            fields.RemoveAll(f => WriteInIsOutsideMaxWriteInsBounds(maxWriteIns, f));
            fields.RemoveAll(f => PersonVotesIsOutsideMaxNumberOfCandidatesBounds(maxNumberOfCandidatesOnList, f));
            return fields.Any(f => f.NeedsVerification)
                   || (ballotPaperValuesInterpreterModification.IsValid.HasValue && !ballotPaperValuesInterpreterModification.IsValid.Value);
        }

        private bool PersonVotesIsOutsideMaxNumberOfCandidatesBounds(int maxNumberOfCandidatesOnList, BallotPaperFieldValue f)
        {
            return (f.IsCandidateField() || f.IsStrikeoutField() || f.IsRenumberField())
                && f.FieldIndex > maxNumberOfCandidatesOnList;
        }

        private static bool WriteInIsOutsideMaxWriteInsBounds(int maxWriteIns, BallotPaperFieldValue f)
        {
            return f.IsWriteInField() && f.FieldIndex > maxWriteIns;
        }

        private int? GetNextVerificationIndex()
        {
            highestVerificationIndex++;
            return highestVerificationIndex;
        }

        public BallotPaperValuesInterpreterModification GetBallotPaper(int ballotPaperId, int ballotPaperConcurrencyToken)
        {
            if (!ballotPapersToUpdate.ContainsKey(ballotPaperId))
            {
                ballotPapersToUpdate.Add(ballotPaperId, new BallotPaperValuesInterpreterModification { BallotPaperConcurrencyToken = ballotPaperConcurrencyToken, BallotPaperId = ballotPaperId });
            }

            return ballotPapersToUpdate[ballotPaperId];
        }

        public void MarkFieldToNeedVerification(BallotPaperFieldValue ballotNumberField)
        {
            ballotNumberField.NeedsVerification = true;
            AddModifiedField(ballotNumberField);
        }

        public void GenerateValidBallotNumber(BallotPaperFieldValue ballotNumberField, string ballotNumber)
        {
            ballotNumberField.Value = ballotNumber;
            ballotNumberField.NeedsVerification = false;
            AddModifiedField(ballotNumberField);
        }

        public void MarkStampFieldToNotNeedVerification(BallotPaperFieldValue ballotNumberField, string fieldValue)
        {
            ballotNumberField.ValueSourceType = ValueSourceType.StateInterpreter;
            ballotNumberField.Value = fieldValue;
            ballotNumberField.NeedsVerification = false;
            AddModifiedField(ballotNumberField);
        }
    }
}
