using System;
using System.Collections.Generic;
using System.Linq;
using Valg.EVA.Skanning.Contracts;
using Valg.EVA.Skanning.Entity.ReadModel;
using Valg.EVA.Skanning.ServicesModel.ElectionConfig.DataProvider;
using Valg.EVA.Skanning.ServicesModel.ElectionConfig.Model;
using Valg.EVA.Skanning.ServicesModel.Internal.BallotNumber;
using Valg.EVA.Skanning.ServicesModel.Internal.Interpreter.Modifications;
using Valg.EVA.Skanning.ServicesModel.Internal.Serializers;

namespace Valg.EVA.Skanning.ServicesModel.Internal.Interpreter
{
    public class WriteInInterpreter
    {
        private readonly ICandidateListDataProvider candidateListDataProvider;
        private readonly IElectionConfigDataProvider electionConfigDataProvider;

        public WriteInInterpreter(ICandidateListDataProvider candidateListDataProvider, IElectionConfigDataProvider electionConfigDataProvider)
        {
            this.candidateListDataProvider = candidateListDataProvider;
            this.electionConfigDataProvider = electionConfigDataProvider;
        }

        public void CheckWriteInIntegrity(
            IReadOnlyCollection<BallotPaperFieldValue> ballotPaperFieldValues,
            InterpreterModificationBuffer interpreterModificationBuffer,
            string boxBarcodeLong)
        {

            var writeInFields = ballotPaperFieldValues.Where(p => p.IsWriteInField()).ToArray();
            var ballotNumber = ballotPaperFieldValues.First(p => p.IsBallotNoField());

            var affiliationShortCode = string.Empty;
            if (!string.IsNullOrEmpty(ballotNumber.Value) && !ballotNumber.NeedsVerification)
            {
                affiliationShortCode = BallotNumberSplitter.GetAffiliationShortCode(ballotNumber.Value);
            }

            var countMetricsForContest = GetCountMetricsForContest(boxBarcodeLong);

            if (IsTrue(countMetricsForContest.HasWriteIns))
            {
                var maxWriteInForContest = GetMaxWriteInForContest(boxBarcodeLong).GetValueOrDefault();

                ValidateWriteInFields(interpreterModificationBuffer, boxBarcodeLong, writeInFields, maxWriteInForContest, affiliationShortCode);
            }
            else
            {
                MarkWriteInFieldsToBeIgnored(interpreterModificationBuffer, writeInFields);
            }
        }

        private bool IsTrue(bool? nullableBool)
        {
            return nullableBool.HasValue && nullableBool.Value;
        }

        private void MarkWriteInFieldsToBeIgnored(InterpreterModificationBuffer interpreterModificationBuffer, BallotPaperFieldValue[] writeInFields)
        {
            foreach (var writeInField in writeInFields)
            {
                writeInField.NeedsVerification = false;
                var writeInData = WriteInDataJsonSerializer.Deserialize(writeInField);
                writeInData.NeedsVerification = false;
                writeInData.NeededVerification = false;
                writeInData.IgnoreWriteIn = true;
                writeInField.SerializeWriteInDataToJson(writeInData);
                interpreterModificationBuffer.AddModifiedField(writeInField);
            }
        }

        private void ValidateWriteInFields(InterpreterModificationBuffer interpreterModificationBuffer, string boxBarcodeLong, BallotPaperFieldValue[] writeInFields, int maxWriteInForContest, string affiliationShortCode)
        {
            foreach (var writeInField in writeInFields)
            {
                var isInsideMaxWriteInBoundsOrMaxWriteInMissing = (maxWriteInForContest == 0 || writeInField.FieldIndex <= maxWriteInForContest);
                if (isInsideMaxWriteInBoundsOrMaxWriteInMissing)
                {
                    var modified = false;
                    var needsVerification = false;

                    if (!writeInField.NeedsVerification
                        && (!WriteInDataJsonSerializer.Deserialize(writeInField).IgnoreWriteIn))
                    {
                        if (ValueExistMoreThanOnce(writeInField, writeInFields))
                        {
                            needsVerification = true;
                        }
                        else
                        {
                            var candidateList = GetValidCandidateListForWriteIn(boxBarcodeLong, affiliationShortCode);

                            if (candidateList.Count == 0)
                            {
                                needsVerification = true;
                            }
                            else
                            {
                                var writeInData = WriteInDataJsonSerializer.Deserialize(writeInField);
                                var candidates = candidateList.Where(p => IsWriteInLevenshteinMatch(p.NameLine, writeInData.ScannerValue)).ToList(); 

                                if (candidates.Count() == 1)
                                {
                                    var candidate = candidates.First();
                                    
                                    writeInData.CandidateName = candidate.NameLine;
                                    writeInData.AffiliationShortCode = candidate.AffiliationShortCode;
                                    writeInData.AffiliationId = candidate.AffiliationId;
                                    writeInData.RankInAffiliation = candidate.RankInAffiliation;
                                    writeInField.SerializeWriteInDataToJson(writeInData);

                                    modified = true;
                                }
                                else
                                {
                                    needsVerification = true;
                                }
                            }
                        }
                    }

                    if (needsVerification || writeInField.NeedsVerification)
                    {
                        writeInField.NeedsVerification = true;
                        var writeInData = WriteInDataJsonSerializer.Deserialize(writeInField);
                        writeInData.NeedsVerification = true;
                        writeInData.NeededVerification = true;
                        writeInField.SerializeWriteInDataToJson(writeInData);

                        modified = true;
                    }

                    if (modified)
                    {
                        interpreterModificationBuffer.AddModifiedField(writeInField);
                    }
                }
            }
        }

        public static bool IsWriteInLevenshteinMatch(string handwrittenText, string candidateName)
        {
            var separators = new char[] {' '};
            var candidateParts = candidateName.Split(separators, StringSplitOptions.RemoveEmptyEntries);
            return GetLevenshteinDistance(handwrittenText.ToLower(), candidateName.ToLower()) <= candidateParts.Length;
        }

        public static int GetLevenshteinDistance(string s, string t)
        {
            var n = s.Length;
            var m = t.Length;
            var d = new int[n + 1, m + 1];

            
            if (n == 0)
            {
                return m;
            }

            if (m == 0)
            {
                return n;
            }

            
            for (var i = 0; i <= n; d[i, 0] = i++)
            {
            }

            for (var j = 0; j <= m; d[0, j] = j++)
            {
            }

            
            for (var i = 1; i <= n; i++)
            {
                
                for (var j = 1; j <= m; j++)
                {
                    
                    var cost = (t[j - 1] == s[i - 1]) ? 0 : 1;

                    
                    d[i, j] = Math.Min(
                        Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1),
                        d[i - 1, j - 1] + cost);
                }
            }
            
            return d[n, m];
        }


        private bool IsWriteInExactMatch(string handwrittenText, string candidateName)
        {
            return string.Compare(handwrittenText, candidateName, StringComparison.CurrentCultureIgnoreCase) == 0;
        }

        private CountMetricsForContest GetCountMetricsForContest(string boxBarcodeLong)
        {
            return electionConfigDataProvider.GetCountMetricsForContest(boxBarcodeLong.GetElectionEventIdFromBarcodeLong(), boxBarcodeLong.GetContestIdFromBarcodeLong());
        }

        private int? GetMaxWriteInForContest(string boxBarcodeLong)
        {
            var maxWriteInCount = electionConfigDataProvider.GetMaxWriteInForContest(boxBarcodeLong.GetElectionEventIdFromBarcodeLong(), boxBarcodeLong.GetContestIdFromBarcodeLong());
            return maxWriteInCount.HasValue ? maxWriteInCount.Value : 0;
        }

        private List<Candidate> GetValidCandidateListForWriteIn(string boxBarcodeLong, string affiliationShortCode)
        {
            return candidateListDataProvider.GetWriteInCandidates(boxBarcodeLong.GetElectionEventIdFromBarcodeLong(), boxBarcodeLong.GetContestIdFromBarcodeLong(), affiliationShortCode);
        }

        private bool ValueExistMoreThanOnce(BallotPaperFieldValue writeInField, IEnumerable<BallotPaperFieldValue> writeInFields)
        {
            return writeInFields.Count(p => string.Compare(WriteInDataJsonSerializer.Deserialize(p).ScannerValue, WriteInDataJsonSerializer.Deserialize(writeInField).ScannerValue, StringComparison.CurrentCultureIgnoreCase) == 0) > 1;
        }
    }
}
