/// <summary>
        /// Gets Product Information Database (PID) from Veterinary Medicines Directorate (VMD)
        /// </summary>
        /// <param name="options"><see cref="PidFactoryOptions"/>Options for processing and storage of PID</param>
        /// <returns></returns>
        public static async Task <VMDPID> GetVmdPid(PidFactoryOptions options = PidFactoryOptions.None)
        {
            if (_vmdpid != null)
            {
                return(_vmdpid);
            }

            var pidUpdated = false;

            //do we have an in-memory copy?
            if (_vmdpid == null || (options & PidFactoryOptions.OverrideCachedCopy) != 0)
            {
                //is there one available on disk?
                var pidDeserialiseSuccess = false;
                if ((options & PidFactoryOptions.PersistentPid) != 0)
                {
                    pidDeserialiseSuccess = GetPidFromDisk();
                    // ReSharper disable once PossibleNullReferenceException
                    if (pidDeserialiseSuccess && await GetPidUpdateDate() > _vmdpid.CreatedDateTime)
                    {
                        _vmdpid = await GetPidFromVmd();

                        pidUpdated         = true;
                        _cachedCopyOptions = options;
                    }
                }

                if (!pidDeserialiseSuccess)
                {
                    _vmdpid = await GetPidFromVmd();

                    pidUpdated         = true;
                    _cachedCopyOptions = options;
                }
            }

            //process target species options
            if ((options & PidFactoryOptions.GetTargetSpeciesForExpiredVmdProduct) != 0 &&
                ((_cachedCopyOptions & PidFactoryOptions.GetTargetSpeciesForExpiredVmdProduct) == 0 ||
                 pidUpdated)
                )
            {
                _vmdpid = await PopulateExpiredProductTargetSpecies(_vmdpid);
            }

            if ((options & PidFactoryOptions.GetTargetSpeciesForExpiredEmaProduct) != 0 &&
                ((_cachedCopyOptions & PidFactoryOptions.GetTargetSpeciesForExpiredEmaProduct) == 0) ||
                pidUpdated)
            {
                _vmdpid = await PopulateExpiredProductTargetSpeciesFromEma(_vmdpid);
            }

            //persist new copy
            if ((options & PidFactoryOptions.PersistentPid) != 0 && pidUpdated)
            {
                Serialize(_vmdpid);
            }

            return(_vmdpid);
        }
 /// <summary>
 /// Writes <see cref="VMDPID"/> to temp file defined in _tf in binary format
 /// </summary>
 /// <param name="pid"></param>
 private static void Serialize(VMDPID pid)
 {
     using (var fs = File.Open(TempFile, FileMode.Create, FileAccess.Write))
     {
         Formatter.Serialize(fs, pid);
     }
 }
        /// <summary>
        /// Downloads SPC documents from VMD website for <see cref="ExpiredProduct"/>s, then uses
        /// <see cref="SPCParser"/> to extract target species
        /// </summary>
        /// <param name="inpid"></param>
        /// <returns>Provided <see cref="VMDPID"/> with populated target species for expired products</returns>
        private static async Task <VMDPID> PopulateExpiredProductTargetSpecies(VMDPID inpid)
        {
            var expiredProducts =
                new ConcurrentBag <ExpiredProduct>(inpid.ExpiredProducts.Where(
                                                       ep => ep.SPC_Link.ToLower().EndsWith(".doc") ||
                                                       ep.SPC_Link.ToLower().EndsWith(".docx")));

            var tasks = expiredProducts.Select(async ep => await PopulateTargetSpecies(ep));
            await Task.WhenAll(tasks);

            inpid.ExpiredProducts = inpid.ExpiredProducts.Where(ep => !(ep.SPC_Link.ToLower().EndsWith(".doc") ||
                                                                        ep.SPC_Link.ToLower().EndsWith(".docx")))
                                    .Union(expiredProducts).ToList();
            return(inpid);
        }
        /// <summary>
        /// Tries to deserialise VMDPID from temp file into _vmdpid
        /// </summary>
        /// <returns>Success</returns>
        private static bool GetPidFromDisk()
        {
            if (!File.Exists(TempFile) || new FileInfo(TempFile).Length == 0)
            {
                return(false);
            }

            using (var fs = File.OpenRead(TempFile))
            {
                try
                {
                    _vmdpid = (VMDPID)Formatter.Deserialize(fs);
                }
                catch (Exception)
                {
                    return(false);
                }
            }

            return(true);
        }
        /// <summary>
        /// Uses <see cref="EPARTools"/> to ascertain which <see cref="ExpiredProduct"/>s in a <see cref="VMDPID"/> are
        /// EMA-authorised, search for an SPC document on the EMA website (as no direct links to documents
        /// are provided in the PID) and then uses <see cref="SPCParser"/> to extract a dictionary of
        /// products and target species. The product in question is matched to this dictionary by name.
        /// </summary>
        /// <param name="inpid"></param>
        /// <returns>The provided <see cref="VMDPID"/> with EMA-authorised expired products' target species populated</returns>
        private static async Task <VMDPID> PopulateExpiredProductTargetSpeciesFromEma(VMDPID inpid)
        {
            var expiredProducts =
                new ConcurrentBag <ExpiredProduct>(inpid.ExpiredProducts.Where(
                                                       ep => EPARTools.IsEPAR(ep.SPC_Link)));

            var tasks = expiredProducts.Select(async ep => await PopulateTargetSpeciesFromEma(ep));
            await Task.WhenAll(tasks);

            inpid.ExpiredProducts = inpid.ExpiredProducts.Where(ep => !EPARTools.IsEPAR(ep.SPC_Link))
                                    .Union(expiredProducts).ToList();

            return(inpid);
        }
        /// <summary>
        /// Converts autogen-from-xsd (VMD_PIDProducts.xsd)
        /// class hierarchy into clean human-created one
        /// </summary>
        /// <param name="raw">As-parsed VMDPID_Raw</param>
        /// <param name="createdDateTime">Created DateTime from xml comment</param>
        /// <returns>Instance of VMDPID containing cleaned data from raw</returns>
        private static VMDPID CleanAndParse(VMDPID_Raw raw, DateTime?createdDateTime)
        {
            var output = new VMDPID
            {
                //TODO: refactor repetitive product processing logic
                CurrentlyAuthorisedProducts = raw.CurrentAuthorisedProducts.Select(rcp =>
                                                                                   new CurrentlyAuthorisedProduct
                {
                    ActiveSubstances     = rcp.ActiveSubstances.Split(',').Select(a => a.Trim().ToLowerInvariant()).ToArray(),
                    AuthorisationRoute   = rcp.AuthorisationRoute.Trim().ToLowerInvariant(),
                    ControlledDrug       = rcp.ControlledDrug,
                    DateOfIssue          = rcp.DateOfIssue,
                    DistributionCategory = rcp.DistributionCategory.Trim().ToLowerInvariant(),
                    //remove stray html tags in Distributor field
                    Distributors       = rcp.Distributors.Replace("&lt;span&gt", "").Split(';').Select(a => a.Trim().ToLowerInvariant()).ToArray(),
                    MAHolder           = rcp.MAHolder.Trim().ToLowerInvariant(),
                    Name               = rcp.Name.Trim().ToLowerInvariant(),
                    PAAR_Link          = rcp.PAAR_Link.Trim().ToLowerInvariant(),
                    PharmaceuticalForm = rcp.PharmaceuticalForm.Trim().ToLowerInvariant(),
                    SPC_Link           = rcp.SPC_Link.Trim().ToLowerInvariant(),
                    TargetSpecies      = rcp.TargetSpecies.Split(',').Select(a => a.Trim().ToLowerInvariant()).ToArray(),
                    TherapeuticGroup   = rcp.TherapeuticGroup.Trim().ToLowerInvariant(),
                    UKPAR_Link         = rcp.UKPAR_Link.Trim().ToLowerInvariant(),
                    VMNo               = rcp.VMNo.Trim().ToLowerInvariant()
                }).ToList(),
                ExpiredProducts = raw.ExpiredProducts.Select(rep =>
                                                             new ExpiredProduct
                {
                    ActiveSubstances   = rep.ActiveSubstances.Split(',').Select(a => a.Trim().ToLowerInvariant()).ToArray(),
                    AuthorisationRoute = rep.AuthorisationRoute.Trim().ToLowerInvariant(),
                    MAHolder           = rep.MAHolder.Trim().ToLowerInvariant(),
                    Name             = rep.Name.Trim().ToLowerInvariant(),
                    SPC_Link         = rep.SPC_Link.Trim().ToLowerInvariant(),
                    VMNo             = rep.VMNo.Trim().ToLowerInvariant(),
                    DateofExpiration = rep.DateOfExpiration
                }).ToList(),
                SuspendedProducts = raw.SuspendedProducts.Select(rsp =>
                                                                 new SuspendedProduct()
                {
                    ActiveSubstances     = rsp.ActiveSubstances.Split(',').Select(a => a.Trim().ToLowerInvariant()).ToArray(),
                    AuthorisationRoute   = rsp.AuthorisationRoute.Trim().ToLowerInvariant(),
                    ControlledDrug       = rsp.ControlledDrug,
                    DateOfIssue          = rsp.DateOfIssue,
                    DistributionCategory = rsp.DistributionCategory.Trim().ToLowerInvariant(),
                    MAHolder             = rsp.MAHolder.Trim().ToLowerInvariant(),
                    Name               = rsp.Name.Trim().ToLowerInvariant(),
                    PAAR_Link          = rsp.PAAR_Link.Trim().ToLowerInvariant(),
                    PharmaceuticalForm = rsp.PharmaceuticalForm.Trim().ToLowerInvariant(),
                    SPC_Link           = rsp.SPC_Link.Trim().ToLowerInvariant(),
                    TargetSpecies      = rsp.TargetSpecies.Split(',').Select(a => a.Trim().ToLowerInvariant()).ToArray(),
                    TherapeuticGroup   = rsp.TherapeuticGroup.Trim().ToLowerInvariant(),
                    UKPAR_Link         = rsp.UKPAR_Link.Trim().ToLowerInvariant(),
                    VMNo               = rsp.VMNo.Trim().ToLowerInvariant(),
                    DateOfSuspension   = rsp.DateOfSuspension
                }).ToList(),
                HomoeopathicProducts = raw.HomeopathicProducts.Select(rhp =>
                                                                      new HomoeopathicProduct
                {
                    ActiveSubstances     = rhp.ActiveSubstances.Split(',').Select(a => a.Trim().ToLowerInvariant()).ToArray(),
                    AuthorisationRoute   = rhp.AuthorisationRoute.Trim().ToLowerInvariant(),
                    ControlledDrug       = rhp.ControlledDrug,
                    DateOfIssue          = rhp.DateOfIssue,
                    DistributionCategory = rhp.DistributionCategory.Trim().ToLowerInvariant(),
                    MAHolder             = rhp.MAHolder.Trim().ToLowerInvariant(),
                    Name = rhp.Name.Trim().ToLowerInvariant(),
                    PharmaceuticalForm = rhp.PharmaceuticalForm.Trim().ToLowerInvariant(),
                    TargetSpecies      = rhp.TargetSpecies.Split(',').Select(a => a.Trim().ToLowerInvariant()).ToArray(),
                    TherapeuticGroup   = rhp.TherapeuticGroup.Trim().ToLowerInvariant(),
                    VMNo = rhp.VMNo.Trim().ToLowerInvariant()
                }).ToList(),
                CreatedDateTime = createdDateTime ?? default(DateTime)
            };

            foreach (var product in output.AllProducts)
            {
                PopulateStaticTypedTargetSpecies(product);
            }

            return(output);
        }