using System;
using System.Collections.Generic;
using System.EnterpriseServices;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BallotTemplates;
using EHF;
using InterpreterLibrary.Models;
using Valg.EVA.Skanning.Contracts;
using Valg.EVA.Skanning.Contracts.CommonEnums;
using Valg.EVA.Skanning.Scan.ReadSoftContracts.Model;
using Valg.EVA.Skanning.Scan.ReadSoftEmulation;
using Field = Valg.EVA.Skanning.Scan.ReadSoftEmulation.Field;



namespace Valg.EVA.Skanning.Scan.ReadSoftEmulation
{
    class FieldMapper
    {
        private readonly BallotPaperOutput _scannedOutput;
        private readonly BallotTemplate _template;
        private readonly Valg.EVA.Skanning.Scan.ReadSoftEmulation.Form _form;
        private float _miniMinimumTesseractConfidence;

        public FieldMapper(BallotPaperOutput scannedOutput, BallotTemplate template, Valg.EVA.Skanning.Scan.ReadSoftEmulation.Form form, float miniMinimumTesseractTesseractConfidence)
        {
            _miniMinimumTesseractConfidence = miniMinimumTesseractTesseractConfidence;
            _scannedOutput = scannedOutput;
            _template = template;
            _form = form;
        }

        public void MapFields()
        {
            var paperType = GetPaperType(TemplateTypeIDToBallotType(_template.TypeId));
            var fieldTypes = new HashSet<ScannableFieldType>(paperType.FieldTypesForThisBallotPaperType);
            if (fieldTypes.Contains(ScannableFieldType.BallotID))
            {
                AddBallotIdField();
            }
            if (fieldTypes.Contains(ScannableFieldType.BallotNo))
            {
                AddBallotNoField();
            }
            if (fieldTypes.Contains(ScannableFieldType.Stamp))
            {
                AddStampFields();
            }
            if (fieldTypes.Contains(ScannableFieldType.Ca_renumber))
            {
                AddRenumberingFields();
            }
            if (fieldTypes.Contains(ScannableFieldType.MarkDecr))
            {
                AddStrikeoutFields();
            }
            if (fieldTypes.Contains(ScannableFieldType.Ref_opt))
            {
                AddSelectionFields();
            }
            if (fieldTypes.Contains(ScannableFieldType.MarkIncr))
            {
                if (paperType.BallotPaperType.IsBlankBallotPaperType())
                {
                    AddBlankFields();
                }
                else
                {
                    AddPersonVotes();
                }
            }
            if (fieldTypes.Contains(ScannableFieldType.Candidate))
            {
                AddWriteInFields();
            }

            if (fieldTypes.Contains(ScannableFieldType.AffiliationName))
            {
                AddAffiliationNames();
            }
        }

        private void AddAffiliationNames()
        {
            short index = 0;
            foreach (var ballotHeader in _scannedOutput.BallotHeaders)
            {
                var field = new Field()
                {
                    Name = ScannableFieldType.AffiliationName.FieldName,
                    Type = ehFieldType.ehDataField,
                    Category = 0,
                    Index = index,
                    Value = ballotHeader.AffiliationName
                };
                _form.AddField(field);
                index++;
            }
        }

        private void AddContestNames()
        {
            short index = 0;
            foreach (var ballotHeader in _scannedOutput.BallotHeaders)
            {
                var field = new Field()
                {
                    Name = ScannableFieldType.ContestName.FieldName,
                    Type = ehFieldType.ehDataField,
                    Category = 0,
                    Index = index,
                    Value = ballotHeader.ContestName
                };
                _form.AddField(field);
                index++;
            }
        }

        private void AddBlankFields()
        {
            var percentage = Convert.ToInt32(_scannedOutput.BlankFillPercentage);

            if (percentage <= _template.Inside.BlankRegion.ThresholdPercent)
                return;
            var field = new Field()
            {
                Name = ScannableFieldType.MarkIncr.FieldName,
                Type = ehFieldType.ehMarkField,
                Category = 0,
                Index = 0,
                Fill = 1
            };
            _form.AddField(field);
        }

        private void AddSelectionFields()
        {
            short candidateNumber = 0;
            if (!_template.Inside.AffiliationSelectionFields.Items.Any())
            {
                return;
            }

            var lastCandidateNumber = _template.Inside.AffiliationSelectionFields.Items.Max(f => f.CandidateNumber);
            var highestCandidateNumberAdded = -1;
            foreach (var rf in _template.Inside.AffiliationSelectionFields.Items)
            {
                ++candidateNumber;
                var mark = _scannedOutput.ListSelections.SingleOrDefault(rn => rn.CandidateNumber == candidateNumber);
                if (!(mark?.FillPercentage > 0))
                    continue;
                var field = new Field()
                {
                    Name = ScannableFieldType.Ref_opt.FieldName,
                    Type = ehFieldType.ehDataField,
                    Category = 0,
                    Value = mark?.Text ?? "",
                    Index = candidateNumber,
                    Fill = Convert.ToInt32(mark?.FillPercentage)
                };
                _form.AddField(field);
                highestCandidateNumberAdded = candidateNumber;
            }

            if (highestCandidateNumberAdded == lastCandidateNumber)
            {
                return;
            }

            OcrResult unlistedAffiliationName = _scannedOutput.UnlistedAffiliationName;
            if (!(unlistedAffiliationName?.InterpretedText?.Length > 0))
            {
                return;
            }
            {
                _form.AddField(new Field()
                {
                    Name = ScannableFieldType.Ref_opt.FieldName,
                    Type = ehFieldType.ehDataField,
                    Category = 0,
                    Value = "X",
                    Index = candidateNumber,
                    Fill = 50
                }
                );
            }

        }

        private void AddBallotIdField()
        {
            var ballotId = _scannedOutput.BallotId;
            var field = new Field()
            {
                Name = ScannableFieldType.BallotID.FieldName,
                Type = ehFieldType.ehDataField,
                Category = 0,
                Index = 0,
                Value = ballotId.InterpretedText ?? ""
            };
            _form.AddField(field);
        }

        private void AddBallotNoField()
        {
            var ballotNo = _scannedOutput.BallotNo;
            var field = new Field()
            {
                Name = ScannableFieldType.BallotNo.FieldName,
                Type = ehFieldType.ehDataField,
                Category = 0,
                Index = 0,
                Value = ballotNo.InterpretedText ?? ""
            };
            if (ballotNo.Confidence < _miniMinimumTesseractConfidence)
            {
                field.Value = ReplaceStringWithStars(ballotNo.InterpretedText);
            }
            _form.AddField(field);
        }

        private void AddStampFields()
        {
            const float tolerance = 0.7f;
            if (_scannedOutput.StampFillPercentages.Count > 1)
            {
                
                for (var i = 0; i < _scannedOutput.StampFillPercentages.Count; ++i)
                {
                    if (_scannedOutput.StampFillPercentages[i] < tolerance)
                    {
                        continue;
                    }

                    var field = new Field()
                    {
                        Name = "ref_single",
                        Type = ehFieldType.ehMarkField,
                        Category = 0,
                        Value = "1", 
                        Index = (short)(i + 1),
                        Fill = ConvertFloatToIntergerAndRoundDown(_scannedOutput.StampFillPercentages[i])
                    };
                    _form.AddField(field);
                }
            }
            else
            {
                var field = new Field()
                {
                    Name = ScannableFieldType.Stamp.FieldName,
                    Type = ehFieldType.ehMarkField,
                    Category = 0,
                    Value = "1", 
                    Index = 1,
                    Fill = ConvertFloatToIntergerAndRoundDown(_scannedOutput.StampFillPercentages[0])
                };
                _form.AddField(field);
            }
        }

        private void AddPersonVotes()
        {
            short candidateNumber = 0;
            foreach (var rf in _template.Inside.PersonVoteFields.Items)
            {
                ++candidateNumber;
                var mark = _scannedOutput.PersonVotes.SingleOrDefault(rn => rn.CandidateNumber == candidateNumber);
                if (!(mark?.FillPercentage > 0))
                    continue;
                var field = new Field()
                {
                    Name = ScannableFieldType.MarkIncr.FieldName,
                    Type = ehFieldType.ehMarkField,
                    Category = 0,
                    Value = mark?.Text ?? "",
                    Index = candidateNumber,
                    Fill = Convert.ToInt32(mark?.FillPercentage)
                };
                _form.AddField(field);
            }
        }

        private void AddRenumberingFields()
        {
            short candidateNumber = 0;
            foreach (var rf in _template.Inside.RenumberingFields.Items)
            {
                ++candidateNumber;
                var mark = _scannedOutput.Renumberings.SingleOrDefault(rn => rn.CandidateNumber == candidateNumber);
                if (!(mark?.FillPercentage > 0))
                    continue;
                var value = string.IsNullOrWhiteSpace(mark.Text) ? "?" : mark.Text;
                var field = new Field()
                {
                    Name = ScannableFieldType.Ca_renumber.FieldName,
                    Type = ehFieldType.ehDataField,
                    Category = 0,
                    Value = value,
                    Index = candidateNumber,
                    Fill = Convert.ToInt32(mark?.FillPercentage)
                };
                _form.AddField(field);
            }
        }

        private void AddStrikeoutFields()
        {
            short candidateNumber = 0;
            foreach (var rf in _template.Inside.StrikeoutFields.Items)
            {
                ++candidateNumber;
                var mark = _scannedOutput.Strikeouts.SingleOrDefault(rn => rn.CandidateNumber == candidateNumber);
                if (!(mark?.FillPercentage > 0))
                    continue;
                var field = new Field()
                {
                    Name = ScannableFieldType.MarkDecr.FieldName,
                    Type = ehFieldType.ehMarkField,
                    Category = 0,
                    Value = mark?.Text ?? "",
                    Index = candidateNumber,
                    Fill = Convert.ToInt32(mark?.FillPercentage)
                };
                _form.AddField(field);
            }
        }

        private string ReplaceStringWithStars(string input)
        {
            string output = "";
            for (int i = 0; i < input.Length; i++)
            {
                output += "*";
            }

            return output;
        }

        private void AddWriteInFields()
        {
            short fieldIndex = 0;
            foreach (var wf in _scannedOutput.WriteIns)
            {
                ++fieldIndex;
                var field = new Field()
                {
                    Name = ScannableFieldType.Candidate.FieldName,
                    
                    
                    Value = wf.Name,
                    Index = fieldIndex,
                    
                    Fill = (int)wf.NameFillPercentage
                };
                _form.AddField(field);
            }
        }

        private int ConvertFloatToIntergerAndRoundDown(float fillPercentage)
        {
            return Convert.ToInt32(Math.Floor(fillPercentage));
        }
        private static BallotType TemplateTypeIDToBallotType(string templateTypeId)
        {
            if (!int.TryParse(templateTypeId, out int type))
                return BallotType.Ukjent;
            if (Enum.IsDefined(typeof(BallotType), type))
            {
                return (BallotType)type;
            }
            return BallotType.Ukjent;
        }


        private static ScannableBallotPaperType GetPaperType(BallotType type)
        {
            switch (type)
            {
                case BallotType.Storting2:
                    return ScannableBallotPaperType.Parliament;
                case BallotType.GenStor2:
                    return ScannableBallotPaperType.ParliamentGeneral;
                case BallotType.StorBlank:
                    return ScannableBallotPaperType.ParliamentBlank;
                case BallotType.FylBlank2:
                    return ScannableBallotPaperType.CountyBlank2;
                case BallotType.Fylke2:
                    return ScannableBallotPaperType.County2;
                case BallotType.GenFylk2:
                    return ScannableBallotPaperType.CountyGeneral;
                case BallotType.GenKomm2:
                    return ScannableBallotPaperType.MunicipalityGeneral;
                case BallotType.GenOslo2:
                    return ScannableBallotPaperType.OsloDistrictGeneral;
                case BallotType.OslBlank2:
                    return ScannableBallotPaperType.OsloDistrictBlank;
                case BallotType.Oslo2:
                    return ScannableBallotPaperType.OsloDistrict;
                case BallotType.Ukjent:
                    return ScannableBallotPaperType.Unknown;
                case BallotType.Kommune2:
                    return ScannableBallotPaperType.Municipality2;
                case BallotType.Kommune3:
                    return ScannableBallotPaperType.Municipality3;
                case BallotType.KomBlank2:
                    return ScannableBallotPaperType.MunicipalityBlank2;
                case BallotType.KomBlank3:
                    return ScannableBallotPaperType.MunicipalityBlank3;
                case BallotType.Kvalitet:
                    return ScannableBallotPaperType.QualityCheck;
                case BallotType.Kommune3X:
                    return ScannableBallotPaperType.Municipality3X;
                case BallotType.Fylke3:
                    return ScannableBallotPaperType.County3;
                case BallotType.FylBlank3:
                    return ScannableBallotPaperType.CountyBlank3;
                default:
                    throw new ArgumentOutOfRangeException(nameof(type), type, null);
            }
        }
    }
}

