using Valg.EVA.Skanning.Repositories.ElectionConfig;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Valg.EVA.Skanning.ServicesModel.Internal;
using Valg.EVA.Skanning.ServicesModel.ElectionConfig.Model;

namespace Valg.EVA.Skanning.ServicesModel.ElectionConfig.DataProvider
{
    public class ElectionConfigDataProvider : IElectionConfigDataProvider
    {
        private readonly IElectionEventRepository electionEventRepository;
        private readonly IElectionConfigXmlDeserializationHelper deserializationHelper;

        private readonly ConcurrentDictionary<string, IList<ElectionEvent>> electionEvents = new ConcurrentDictionary<string, IList<ElectionEvent>>();
        private readonly ConcurrentDictionary<string, IList<Election>> elections = new ConcurrentDictionary<string, IList<Election>>();
        private readonly ConcurrentDictionary<string, IList<VotingCategory>> votingCategories = new ConcurrentDictionary<string, IList<VotingCategory>>();
        private readonly ConcurrentDictionary<string, IList<ReportingUnit>> reportingUnits = new ConcurrentDictionary<string, IList<ReportingUnit>>();

        private readonly ConcurrentDictionary<string, IList<Contest>> contests = new ConcurrentDictionary<string, IList<Contest>>();
        private readonly ConcurrentDictionary<string, CountMetricsForContest> countMetricsForContests = new ConcurrentDictionary<string, CountMetricsForContest>();
        private readonly ConcurrentDictionary<string, List<VotingCategory>> votingCategoryInContest = new ConcurrentDictionary<string, List<VotingCategory>>();
        private readonly ConcurrentDictionary<string, List<Contest>> contestInAreaContext = new ConcurrentDictionary<string, List<Contest>>();
        private readonly ConcurrentDictionary<string, Election> electionByContestId = new ConcurrentDictionary<string, Election>();
        private readonly ConcurrentDictionary<string, int?> maxWriteInForContest = new ConcurrentDictionary<string, int?>();

        public ElectionConfigDataProvider(IElectionEventRepository electionEventRepository, IElectionConfigXmlDeserializationHelper deserializationHelper)
        {
            this.electionEventRepository = electionEventRepository;
            this.deserializationHelper = deserializationHelper;
        }

        public ElectionEvent GetElectionEvent(string electionEventId)
        {
            if (electionEventId.IsStringActuallyLongOrInt())
            {
                return electionEvents.GetOrAdd(electionEventId, LoadElectionEventData).SingleOrDefault(x => x.Id == electionEventId);
            }

            return null;
        }

        public List<ElectionEvent> GetAllElectionEvents()
        {
            var allElectionEvents = new List<ElectionEvent>();
            var allElectionIds = electionEventRepository.GetAllElectionEventIds();
            foreach (var id in allElectionIds)
            {
                var electionEvent = GetElectionEvent(id.ToString());
                if (electionEvent != null)
                {
                    allElectionEvents.Add(electionEvent);
                }
            }

            return allElectionEvents;
        }

        public Election GetElection(string electionEventId, string electionId)
        {
            if (electionEventId.IsStringActuallyLongOrInt() && electionId.IsStringActuallyLongOrInt())
            {
                return elections.GetOrAdd(electionEventId, LoadElectionData)
                    .SingleOrDefault(x => x.Id == electionId);
            }

            return null;
        }

        public VotingCategory GetVotingCategory(string electionEventId, string pollingDistrictId, string votingCategoryType)
        {
            if (electionEventId.IsStringActuallyLongOrInt() && pollingDistrictId.IsStringActuallyLongOrInt())
            {
                return votingCategories.GetOrAdd(electionEventId, LoadVotingCategoryData)
                    .FirstOrDefault(x => x.Id == pollingDistrictId.GetPollingDistrictIdEmlFormat() && x.Code == votingCategoryType);
            }

            return null;
        }

        public ReportingUnit GetReportingUnit(string electionEventId, string reportingUnitId)
        {
            string reportingUnitIdOnEmlFormat = reportingUnitId.Contains(".") ? reportingUnitId : reportingUnitId.GetReportingUnitIdEmlFormat();

            if (electionEventId.IsStringActuallyLongOrInt())
            {
                return reportingUnits.GetOrAdd(electionEventId, LoadReportingUnitData)
                    .FirstOrDefault(x => x.Id.EndsWith(reportingUnitIdOnEmlFormat));
            }

            return null;
        }

        public Contest GetContest(string electionEventId, string contestId)
        {
            if (electionEventId.IsStringActuallyLongOrInt() && contestId.IsStringActuallyLongOrInt())
            {
                return contests.GetOrAdd(electionEventId, LoadContestData)
                    .SingleOrDefault(x => x.Id == contestId);
            }

            return null;
        }

        public CountMetricsForContest GetCountMetricsForContest(string electionEventId, string contestId)
        {
            if (electionEventId.IsStringActuallyLongOrInt() && contestId.IsStringActuallyLongOrInt())
            {
                var cacheKey = electionEventId + contestId;
                return countMetricsForContests.GetOrAdd(cacheKey, p => LoadCountMetricsForContest(electionEventId, contestId));
            }

            return null;
        }

        public int? GetMaxWriteInForContest(string electionEventId, string contestId)
        {
            if (electionEventId.IsStringActuallyLongOrInt() && contestId.IsStringActuallyLongOrInt())
            {
                var cacheKey = electionEventId + contestId;
                return maxWriteInForContest.GetOrAdd(cacheKey, p => LoadMaxWriteInForContest(electionEventId, contestId));
            }
            return null;
        }

        public List<Contest> GetContestsInArea(string electionEventId, string areaContextId)
        {
            if (electionEventId.IsStringActuallyLongOrInt())
            {
                var cacheKey = electionEventId + areaContextId;
                return contestInAreaContext.GetOrAdd(cacheKey, p => LoadContestByAreaContextId(electionEventId, areaContextId));
            }

            return new List<Contest>();
        }

        public List<Contest> GetContestsInElection(string electionEventId)
        {
            if (electionEventId.IsStringActuallyLongOrInt())
            {
                return contests.GetOrAdd(electionEventId, LoadContestData).ToList();
            }

            return new List<Contest>();
        }

        public List<VotingCategory> GetVotingCategoriesInContest(string electionEventId, string contestId, string areaContextFilter = null)
        {
            if (electionEventId.IsStringActuallyLongOrInt() && contestId.IsStringActuallyLongOrInt())
            {
                var cacheKey = electionEventId + contestId;
                var votingCategoriesInContest = votingCategoryInContest.GetOrAdd(cacheKey, p => LoadVotingCategoriesInContest(electionEventId, contestId));
                if (areaContextFilter == null)
                {
                    return votingCategoriesInContest;
                }
                else
                {
                    return votingCategoriesInContest.Where(p => p.Id.StartsWith(areaContextFilter)).ToList();
                }
            }
            return null;
        }

        public Election GetElectionByContestId(string electionEventId, string contestId)
        {
            if (electionEventId.IsStringActuallyLongOrInt() && contestId.IsStringActuallyLongOrInt())
            {
                var cacheKey = electionEventId + contestId;
                return electionByContestId.GetOrAdd(cacheKey, p => LoadElectionByContestId(electionEventId, contestId));
            }

            throw new InvalidOperationException();
            
        }
        
        private Election LoadElectionByContestId(string electionEventId, string contestId)
        {
            var xmlResults = electionEventRepository.GetElectionByContestId(electionEventId, contestId);
            return deserializationHelper.DeserializeElection(xmlResults);
        }

        private List<ElectionEvent> LoadElectionEventData(string electionEventId)
        {
            var xmlResults = electionEventRepository.GetElectionEvents(electionEventId);
            return deserializationHelper.DeserializeElectionEvent(xmlResults);            
        }

        private List<Contest> LoadContestData(string electionEventId)
        {
            var xmlResults = electionEventRepository.GetContests(electionEventId);
            return deserializationHelper.DeserializeContests(xmlResults);
        }

        private CountMetricsForContest LoadCountMetricsForContest(string electionEventId, string contestId)
        {
            var xmlResults = electionEventRepository.GetCountMetricsForContest(electionEventId, contestId);
            return deserializationHelper.DeserializeCountMetricsForContests(xmlResults, contestId);
        }

        private int? LoadMaxWriteInForContest(string electionEventId, string contestId)
        {
            var xmlResult = electionEventRepository.GetMaxWriteInForContest(electionEventId, contestId);
            return deserializationHelper.DeserializeMaxWriteIn(xmlResult);
        }

        private List<Election> LoadElectionData(string electionEventId)
        {
            var xmlResults = electionEventRepository.GetElections(electionEventId);
            return deserializationHelper.DeserializeElections(xmlResults);
        }

        private List<VotingCategory> LoadVotingCategoriesInContest(string electionEventId, string contestId)
        {
            var xmlResults = electionEventRepository.GetVotingCategoriesInContest(electionEventId, contestId);
            return deserializationHelper.DeserializeVotingCategories(xmlResults);
        }

        private List<VotingCategory> LoadVotingCategoryData(string electionEventId)
        {
            var xmlResults = electionEventRepository.GetVotingCategories(electionEventId);
            return deserializationHelper.DeserializeVotingCategories(xmlResults);
        }

        private List<ReportingUnit> LoadReportingUnitData(string electionEventId)
        {
            var xmlResults = electionEventRepository.GetReportingUnits(electionEventId);
            return deserializationHelper.DeserializeReportingUnits(xmlResults);
        }

        private List<Contest> LoadContestByAreaContextId(string electionEventId, string areaContextId)
        {
            var xmlResults = electionEventRepository.GetContestByAreaContextId(electionEventId, areaContextId);
            return deserializationHelper.DeserializeContests(xmlResults);
        }
    }
}
