using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;
using System.Xml;

class ElectionDayResults
{
    public XmlDocument ParishVotes { get; set; }
    public XmlDocument PrecinctVotes { get; set; }
}

static class ElectionResultsReader
{
    private const string BASE_API_ADDRESS = "https://voterportal.sos.la.gov/api/MediaRequests/";
    private const string FORMATTED_ELECTION_DATE = "2017-03-25";
    private const string ACCESS_KEY = "<Your Access Key Here>";
    private const string SECRET_KEY = "<Your Secret Key Here>";

    private static readonly HttpClient httpClient;
    private static DateTime? latestSavedVersion;


    static ElectionResultsReader()
    {
        httpClient = new HttpClient
        {
            BaseAddress = new Uri(BASE_API_ADDRESS),
        };
    }

    /// <summary>
    /// Returns an XmlDocument with a list of races and candidates for the election.
    /// </summary>
    /// <remarks>This data does not change often and should be requested infrequently.</remarks>
    public static async Task<XmlDocument> ReadRacesAndCandidates()
    {
        return await GetXmlFile("RacesAndCandidates");
    }

    /// <summary>
    /// If new election day results are available, returns them as a pair of XML documents: 
    /// ParishVotes and PrecinctVotes. If new results are not available, returns null.
    /// </summary>
    public static async Task<ElectionDayResults> ReadElectionDayResults(bool includePrecinctLevelVotes)
    {
        ElectionDayResults results = null;

        // Determine if a new version of the election results is available.
        DateTime? currentResultsVersion = await GetCurrentResultsVersion();
        bool isNewVersion = currentResultsVersion.HasValue &&
                            (latestSavedVersion == null || currentResultsVersion.Value > latestSavedVersion.Value);

        // Only request election results when there is a new version. This avoids unnecessary API requests.
        if (isNewVersion)
        {
            latestSavedVersion = currentResultsVersion;

            // Request Election Results
            XmlDocument parishVotesXml = await GetXmlFile("ParishVotes");
            XmlDocument precinctVotesXml = includePrecinctLevelVotes ? await GetXmlFile("PrecinctVotes") : null;

            results = new ElectionDayResults
            {
                ParishVotes = parishVotesXml,
                PrecinctVotes = precinctVotesXml
            };
        }

        return results;
    }


    private static async Task<XmlDocument> GetXmlFile(string fileName)
    {
        // Request XML File
        string requestUrl = string.Format("{0}/{1}/{2}/{3}", fileName, FORMATTED_ELECTION_DATE, ACCESS_KEY, SECRET_KEY);
        HttpResponseMessage response = await httpClient.GetAsync(requestUrl);

        // If the request was not successful, throw an exception detailing why.
        if (!response.IsSuccessStatusCode)
        {
            string responseContent = response.Content.ReadAsStringAsync().Result;
            string errorMessage = string.Format("{0} ({1}): {2}", response.StatusCode, (int)response.StatusCode, responseContent);
            throw new HttpException((int)response.StatusCode, errorMessage);
        }

        // Extract XML from HttpResponse
        Stream xmlStream = await response.Content.ReadAsStreamAsync();
        XmlDocument xmlFile = new XmlDocument();
        xmlFile.Load(xmlStream);

        return xmlFile;
    }

    private static async Task<DateTime?> GetCurrentResultsVersion()
    {
        DateTime? currentResultsVersion = null;

        // Retrieve XML File Containing Version Information
        XmlDocument latestVersionXml = await GetXmlFile("LatestVersion");

        // Retrieve datetime string from version XML.
        string dateString = latestVersionXml["Version"] != null ? latestVersionXml["Version"].InnerText : null;

        // Attempt to parse a date from the XML contents.
        if (!string.IsNullOrWhiteSpace(dateString))
        {
            DateTime version;
            if (DateTime.TryParse(dateString, out version))
                currentResultsVersion = version;
        }

        return currentResultsVersion;
    }
}