using System.Collections.ObjectModel;
using Valg.EVA.Skanning.Entity.SerializedEntities.CountResultModel;
using System.Collections.Generic;
using System.Linq;
using Valg.EVA.Skanning.Oasis.EMLVote;

namespace Valg.EVA.Skanning.Services.CountResult.XmlBuilders
{
    public class VoteXmlBuilder : EmlBaseBuilder
    {
        public EML CreateCountResult(ReadOnlyCollection<CastVote> castVotes, string reportingUnitIdentifierOfCaller)
        {
            if (HasCastVotesWithModifications(castVotes) || castVotes.Any(HasRejections))
            {
                var emlDocument = CreateEmlVoteDocument();
                ElectionMetaData electionMetaData = GetElectionMetaData(castVotes, reportingUnitIdentifierOfCaller);
                AddIndividualCastVoteResults(emlDocument, castVotes, electionMetaData);
                return emlDocument;
            }
            else
            {
                return null;
            }
        }

        private void AddIndividualCastVoteResults(EML emlDocument, ReadOnlyCollection<CastVote> castVotes, ElectionMetaData electionMetaData)
        {
            var castVoteElements = CreateCastVoteElements(castVotes, electionMetaData);

            emlDocument.Votes.CastVote = castVoteElements.ToArray();
        }

        private List<VotesCastVote> CreateCastVoteElements(ReadOnlyCollection<CastVote> castVotes, ElectionMetaData electionMetaData)
        {
            var castVotesToAddToVoteXml = castVotes.Where(p => p.IsModified || (p.ProposedRejection != CastVote.RejectionReasonCode.NotRejected || p.InDoubt == true));

            return castVotesToAddToVoteXml.Select(castVote => CreateCastVoteElements(castVote, electionMetaData)).ToList();
        }

        private VotesCastVote CreateCastVoteElements(CastVote castVote, ElectionMetaData electionMetaData)
        {
            var election = CreateElection(ref electionMetaData, castVote);

            var eventIdentifier = CreateEventIdentifier(ref electionMetaData);

            var ballotIdentifier = CreateBallotIdentifier(castVote);

            var reportingUnit = new ReportingUnitIdentifierStructure
            {
                Id = electionMetaData.ReportingUnitIdentifier
            };

            var rejectionReason = CreateRejectionReason(castVote);

            return new VotesCastVote
            {
                BallotIdentifier = ballotIdentifier,
                Category = electionMetaData.VotingCategory,
                EventIdentifier = eventIdentifier,
                Election = new[] { election },
                ReportingUnitIdentifier = new[] { reportingUnit },
                Item = rejectionReason
            };
        }

        private BallotIdentifierStructure CreateBallotIdentifier(CastVote castVote)
        {
            var ballotName = HasRejections(castVote) ? string.Format("images/Ballot{0}.tiff", castVote.BallotDbId) : null;

            var ballotIdentifier = new BallotIdentifierStructure
            {
                Id = castVote.BallotDbId.ToString(),
                BallotName = ballotName
            };
            return ballotIdentifier;
        }

        private object CreateRejectionReason(CastVote castVote)
        {
            if (castVote.ProposedRejection != CastVote.RejectionReasonCode.NotRejected)
            {
                return new VotesCastVoteProposedRejection
                {
                    ReasonCode = castVote.ProposedRejection.ToString(),
                    Value = YesNoType.yes
                };
            }
            else if (castVote.InDoubt)
            {
                return new VotesCastVoteProposedRejection
                {
                    ReasonCode = CastVote.RejectionReasonCode.FA.ToString(),
                    Value = YesNoType.yes
                };
            }
            else
            {
                return null;
            }
        }

        private CastVoteStructureElection CreateElection(ref ElectionMetaData electionMetaData, CastVote castVote)
        {
            var contest = CreateContest(ref electionMetaData, castVote);

            var electionGroup = new ElectionGroupStructure
            {
                Id = electionMetaData.ElectionGroup
            };

            var electionIdentifier = new ElectionIdentifierStructure
            {
                Id = electionMetaData.ElectionIdentifier,
                ElectionGroup = electionGroup
            };

            var election = new CastVoteStructureElection
            {
                ElectionIdentifier = electionIdentifier,
                Contest = contest
            };
            return election;
        }

        private CastVoteStructureElectionContest CreateContest(ref ElectionMetaData electionMetaData, CastVote castVote)
        {
            var selections = CreateSelections(castVote);

            var contestIdentifier = new ContestIdentifierStructure
            {
                Id = electionMetaData.ContestId
            };

            return new CastVoteStructureElectionContest
            {
                ContestIdentifier = contestIdentifier,
                Selection = selections
            };
        }

        private CastVoteStructureElectionContestSelection[] CreateSelections(CastVote castVote)
        {
            var selections = new List<CastVoteStructureElectionContestSelection>();

            if (HasRejections(castVote))
            {
                AddEmptyAffiliationSelection(selections);
            }
            else
            {
                AddAffiliationSelection(castVote, selections);
                AddPersonalVoteSelections(castVote, selections);
                AddWriteInSelections(castVote, selections);
                AddRenumberSelections(castVote, selections);
                AddStrikeoutSelections(castVote, selections);
            }

            return selections.ToArray();
        }

        private void AddStrikeoutSelections(CastVote castVote, List<CastVoteStructureElectionContestSelection> selections)
        {
            if (castVote.HasStrikeouts)
            {
                foreach (var strikeout in castVote.Strikeouts)
                {
                    var strikeoutSelection = new CastVoteStructureElectionContestSelection
                    {
                        ShortCode = "strikeout",
                        Items = new[] { CreateCandidateIdentifier(strikeout) },
                        ItemsElementName = new[] { ItemsChoiceType6.CandidateIdentifier }
                    };
                    selections.Add(strikeoutSelection);
                }
            }
        }

        private void AddRenumberSelections(CastVote castVote, List<CastVoteStructureElectionContestSelection> selections)
        {
            if (castVote.HasRenumberings)
            {
                foreach (var renumbering in castVote.Renumberings)
                {
                    var renumberingSelection = new CastVoteStructureElectionContestSelection
                    {
                        ShortCode = "renumber",
                        Value = renumbering.RenumberedPosition,
                        Items = new object[] { CreateCandidateIdentifier(renumbering) },
                        ItemsElementName = new[] { ItemsChoiceType6.CandidateIdentifier }
                    };
                    selections.Add(renumberingSelection);
                }
            }
        }

        private void AddWriteInSelections(CastVote castVote, List<CastVoteStructureElectionContestSelection> selections)
        {
            if (castVote.HasWriteIns)
            {
                foreach (var writeIn in castVote.WriteIns)
                {
                    var personVoteSelection = new CastVoteStructureElectionContestSelection
                    {
                        ShortCode = "writein",
                        Items = new object[] { CreateCandidateIdentifier(writeIn), CreateAffiliationIdentifier(writeIn.AffiliationIdentifier) },
                        ItemsElementName = new[] { ItemsChoiceType6.CandidateIdentifier, ItemsChoiceType6.AffiliationIdentifier }
                    };
                    selections.Add(personVoteSelection);
                }
            }
        }

        private void AddPersonalVoteSelections(CastVote castVote, List<CastVoteStructureElectionContestSelection> selections)
        {
            if (castVote.HasPersonalVotes)
            {
                foreach (var personalVote in castVote.PersonalVotes)
                {
                    var personVoteSelection = new CastVoteStructureElectionContestSelection
                    {
                        ShortCode = "personal",
                        Items = new object[] { CreateCandidateIdentifier(personalVote) },
                        ItemsElementName = new[] { ItemsChoiceType6.CandidateIdentifier }
                    };
                    selections.Add(personVoteSelection);
                }
            }
        }

        private CandidateIdentifierStructure CreateCandidateIdentifier(Strikeout strikeout)
        {
            return new CandidateIdentifierStructure
            {
                ShortCode1 = strikeout.RankInAffiliation
            };
        }

        private CandidateIdentifierStructure CreateCandidateIdentifier(PersonalVote personalVote)
        {
            return new CandidateIdentifierStructure
            {
                ShortCode1 = personalVote.RankInAffiliation
            };
        }

        private CandidateIdentifierStructure CreateCandidateIdentifier(WriteIn writeIn)
        {
            return new CandidateIdentifierStructure
            {
                ShortCode1 = writeIn.RankInAffiliation
            };
        }

        private CandidateIdentifierStructure CreateCandidateIdentifier(Renumber renumber)
        {
            return new CandidateIdentifierStructure
            {
                ShortCode1 = renumber.RankInAffiliation
            };
        }

        private void AddAffiliationSelection(CastVote castVote, List<CastVoteStructureElectionContestSelection> selections)
        {
            var affiliationSelection = new CastVoteStructureElectionContestSelection
            {
                Items = new object[] { CreateAffiliationIdentifier(castVote.AffiliationIdentifier) },
                ItemsElementName = new[] { ItemsChoiceType6.AffiliationIdentifier }
            };

            selections.Add(affiliationSelection);
        }

        private void AddEmptyAffiliationSelection(List<CastVoteStructureElectionContestSelection> selections)
        {
            var affiliationSelection = new CastVoteStructureElectionContestSelection
            {
                Items = new object[] { new AffiliationIdentifierStructure { RegisteredName = "" } },
                ItemsElementName = new[] { ItemsChoiceType6.AffiliationIdentifier }
            };

            selections.Add(affiliationSelection);
        }

        private AffiliationIdentifierStructure CreateAffiliationIdentifier(AffiliationIdentifier affiliationIdentifier)
        {
            return new AffiliationIdentifierStructure
            {
                Id = affiliationIdentifier.AffiliationId,
                ShortCode = affiliationIdentifier.AffiliationShortCode,
                RegisteredName = ""
            };
        }

        private EventIdentifierStructure CreateEventIdentifier(ref ElectionMetaData electionMetaData)
        {
            var eventQualifier = new EventQualifierStructure
            {
                Id = electionMetaData.AreaContextId
            };

            var eventIdentifier = new EventIdentifierStructure
            {
                Id = electionMetaData.EventIdentifier,
                EventQualifier = eventQualifier
            };
            return eventIdentifier;
        }

        private EML CreateEmlVoteDocument()
        {
            return new EML
            {
                Id = "460",
                SchemaVersion = "5.0",
                TransactionId = "1",
                Votes = new Votes()
            };
        }

        private bool HasCastVotesWithModifications(IEnumerable<CastVote> countingResults)
        {
            return countingResults.Any(p => p.IsModified);
        }

        private bool HasRejections(CastVote castVote)
        {
            return castVote.InDoubt || castVote.ProposedRejection != CastVote.RejectionReasonCode.NotRejected;
        }
    }
}
