/// <summary> Adds pronunciation audio of a word to be written out to lift </summary>
        private void AddAudio(LexEntry entry, Word wordEntry, string path)
        {
            foreach (var audioFile in wordEntry.Audio)
            {
                var lexPhonetic = new LexPhonetic();

                var util = new Utilities();

                var projectPath = Path.Combine(util.GenerateFilePath(
                                                   Utilities.FileType.Dir, true, "", ""), _projectId);
                projectPath = Path.Combine(projectPath, "Import", "ExtractedLocation");
                var extractedDir = Directory.GetDirectories(projectPath);
                projectPath = Path.Combine(projectPath, extractedDir.Single());
                var src = Path.Combine(util.GenerateFilePath(
                                           Utilities.FileType.Audio, true), Path.Combine(projectPath, "audio", audioFile));
                var dest = Path.Combine(path, audioFile);

                if (File.Exists(src))
                {
                    File.Copy(src, dest, true);

                    var proMultiText = new LiftMultiText {
                        { "href", dest }
                    };
                    lexPhonetic.MergeIn(MultiText.Create(proMultiText));
                    entry.Pronunciations.Add(lexPhonetic);
                }
            }
        }
        /// <summary> The meat of lift import is done here. This reads in all necessary attributes of a word and adds it to the database. </summary>
        public async void FinishEntry(LiftEntry entry)
        {
            var newWord = new Word();
            var proj    = _projService.GetProject(_projectId).Result;

            // Only used when importing semantic domains from a lift-ranges file
            if (_sdList.Count != 0 && proj.SemanticDomains.Count == 0)
            {
                proj.SemanticDomains = _sdList;
                await _projService.Update(_projectId, proj);
            }

            // Add vernacular
            // TODO: currently we just add the first listed option, we may want to choose eventually
            if (!entry.CitationForm.IsEmpty) // Prefer citation form for vernacular
            {
                newWord.Vernacular = entry.CitationForm.FirstValue.Value.Text;
                if (proj.VernacularWritingSystem == "")
                {
                    proj.VernacularWritingSystem = entry.CitationForm.FirstValue.Key;
                    await _projService.Update(_projectId, proj);
                }
            }
            else if (!entry.LexicalForm.IsEmpty) // lexeme form for backup
            {
                newWord.Vernacular = entry.LexicalForm.FirstValue.Value.Text;
                if (proj.VernacularWritingSystem == "")
                {
                    proj.VernacularWritingSystem = entry.LexicalForm.FirstValue.Key;
                    await _projService.Update(_projectId, proj);
                }
            }
            else // this is not a word if there is no vernacular
            {
                return;
            }

            // This is not a word if there are no senses
            if (entry.Senses.Count == 0)
            {
                return;
            }

            // Add senses
            newWord.Senses = new List <Sense>();
            foreach (var sense in entry.Senses)
            {
                var newSense = new Sense {
                    SemanticDomains = new List <SemanticDomain>(), Glosses = new List <Gloss>()
                };

                // Add glosses
                foreach ((var key, var value) in sense.Gloss)
                {
                    newSense.Glosses.Add(new Gloss {
                        Language = key, Def = value.Text
                    });
                }

                // Find semantic domains
                var semanticDomainStrings = new List <string>();
                foreach (var trait in sense.Traits)
                {
                    if (trait.Name.StartsWith("semantic-domain"))
                    {
                        semanticDomainStrings.Add(trait.Value);
                    }
                }

                // Add semantic domains
                foreach (var semanticDomainString in semanticDomainStrings)
                {
                    // Splits on the space between the number and name of the semantic domain
                    var splitSemDom = semanticDomainString.Split(" ", 2);
                    newSense.SemanticDomains.Add(new SemanticDomain {
                        Id = splitSemDom[0], Name = splitSemDom[1]
                    });
                }

                newWord.Senses.Add(newSense);
            }

            // Add plural
            foreach (var field in entry.Fields)
            {
                if (field.Type == "Plural")
                {
                    foreach (var plural in field.Content)
                    {
                        var PluralForm = entry.Fields.First().Content.First().Value.Text;
                        newWord.Plural = PluralForm;
                    }
                }
            }

            // Get path to dir containing local lift package ~/{projectId}/Import/ExtractedLocation
            var util      = new Utilities();
            var importDir = util.GenerateFilePath(
                Utilities.FileType.Dir, false, "", Path.Combine(_projectId, "Import"));
            var extractedPathToImport = Path.Combine(importDir, "ExtractedLocation");

            // Get path to directory with audio files ~/{projectId}/Import/ExtractedLocation/{liftName}/audio
            var importListArr     = Directory.GetDirectories(extractedPathToImport);
            var extractedAudioDir = Path.Combine(importListArr.Single(), "audio");

            // Only add audio if the files exist
            if (Directory.Exists(extractedAudioDir))
            {
                // Add audio
                foreach (var pro in entry.Pronunciations)
                {
                    // get path to audio file in lift package at
                    // ~/{projectId}/Import/ExtractedLocation/{liftName}/audio/{audioFile}.mp3
                    var audioFile = pro.Media.First().Url;
                    newWord.Audio.Add(audioFile);
                }
            }

            newWord.ProjectId = _projectId;
            await _repo.Create(newWord);
        }
        /// <summary> Exports information from a project to a lift package zip </summary>
        public string LiftExport(string projectId)
        {
            // The helper tag must be included because there are also SIL.Utilities.
            var util = new Utilities();

            // Generate the zip dir.
            var exportDir = util.GenerateFilePath(Utilities.FileType.Dir, true, "",
                                                  Path.Combine(projectId, "Export"));

            if (Directory.Exists(Path.Combine(exportDir, "LiftExport")))
            {
                Directory.Delete(Path.Combine(exportDir, "LiftExport"), true);
            }
            var zipDir = Path.Combine(exportDir, "LiftExport", "Lift");

            Directory.CreateDirectory(zipDir);

            // Add audio dir inside zip dir.
            var audioDir = Path.Combine(zipDir, "audio");

            Directory.CreateDirectory(audioDir);
            var liftPath = Path.Combine(zipDir, "NewLiftFile.lift");

            // noBOM will work with PrinceXML
            var liftWriter = new CombineLiftWriter(liftPath, ByteOrderStyle.BOM);
            var rangesDest = Path.Combine(zipDir, "NewLiftFile.lift-ranges");

            // write header of lift document
            var header =
                $@"
                    <ranges>
                        <range id = ""semantic-domain-ddp4"" href = ""{rangesDest}""/>
                    </ranges>
                    <fields>
                        <field tag = ""Plural"">
                            <form lang = ""en""><text></text></form>
                            <form lang = ""qaa-x-spec""><text> Class = LexEntry; Type = String; WsSelector = kwsVern </text></form>
                        </field>
                    </fields>
                ";

            liftWriter.WriteHeader(header);

            // Write out every word with all of its information
            var allWords    = _repo.GetAllWords(projectId).Result;
            var frontier    = _repo.GetFrontier(projectId).Result;
            var activeWords = frontier.Where(x => x.Senses.First().Accessibility == (int)State.Active).ToList();

            // TODO: this is wrong, deleted is a subset of active, are not exclusive
            var deletedWords = allWords.Where(x => activeWords.Contains(x)).ToList();

            foreach (var wordEntry in activeWords)
            {
                var entry = new LexEntry();

                AddVern(entry, wordEntry, projectId);
                AddSenses(entry, wordEntry);
                AddAudio(entry, wordEntry, audioDir);

                liftWriter.Add(entry);
            }
            foreach (var wordEntry in deletedWords)
            {
                var entry = new LexEntry();

                AddVern(entry, wordEntry, projectId);
                AddSenses(entry, wordEntry);
                AddAudio(entry, wordEntry, audioDir);

                liftWriter.AddDeletedEntry(entry);
            }

            liftWriter.End();

            // Export semantic domains to lift-ranges
            var proj = _projService.GetProject(projectId).Result;
            var extractedPathToImport = Path.Combine(GetProjectDir(projectId), "Import", "ExtractedLocation");
            var importLiftDir         = "";

            if (Directory.Exists(extractedPathToImport))
            {
                importLiftDir = Directory.GetDirectories(extractedPathToImport).Select(Path.GetFileName).ToList().Single();
            }
            var rangesSrc = Path.Combine(extractedPathToImport, importLiftDir, $"{importLiftDir}.lift-ranges");

            // If there are no new semantic domains, and the old lift-ranges file is still around, just copy it
            if (proj.SemanticDomains.Count == 0 && File.Exists(rangesSrc))
            {
                File.Copy(rangesSrc, rangesDest, true);
            }
            else // Make a new lift-ranges file
            {
                var liftRangesWriter = XmlWriter.Create(rangesDest, new XmlWriterSettings
                {
                    Indent = true,
                    NewLineOnAttributes = true
                });
                liftRangesWriter.WriteStartDocument();
                liftRangesWriter.WriteStartElement("lift-ranges");
                liftRangesWriter.WriteStartElement("range");
                liftRangesWriter.WriteAttributeString("id", "semantic-domain-ddp4");

                // Pull from resources file with all English semantic domains
                var    assembly = typeof(LiftService).GetTypeInfo().Assembly;
                var    resource = assembly.GetManifestResourceStream("BackendFramework.Data.sdList.txt");
                string sdList;
                using (var reader = new StreamReader(resource, Encoding.UTF8))
                {
                    sdList = reader.ReadToEndAsync().Result;
                }
                var sdLines = sdList.Split(Environment.NewLine);
                foreach (var line in sdLines)
                {
                    if (line != "")
                    {
                        var items = line.Split("`");
                        WriteRangeElement(liftRangesWriter, items[0], items[1], items[2], items[3]);
                    }
                }

                // Pull from new semantic domains in project
                foreach (var sd in proj.SemanticDomains)
                {
                    WriteRangeElement(liftRangesWriter, sd.Id, Guid.NewGuid().ToString(), sd.Name, sd.Description);
                }

                liftRangesWriter.WriteEndElement(); //end semantic-domain-ddp4 range
                liftRangesWriter.WriteEndElement(); //end lift-ranges
                liftRangesWriter.WriteEndDocument();

                liftRangesWriter.Flush();
                liftRangesWriter.Close();
            }


            // Export character set to ldml
            var ldmlDir = Path.Combine(zipDir, "WritingSystems");

            Directory.CreateDirectory(ldmlDir);
            if (proj.VernacularWritingSystem != "")
            {
                LdmlExport(ldmlDir, proj.VernacularWritingSystem);
            }

            // Compress everything
            var destinationFileName = Path.Combine(exportDir,
                                                   Path.Combine($"LiftExportCompressed-{proj.Id}_{string.Format("{0:yyyy-MM-dd_hh-mm-ss}", DateTime.Now)}.zip"));

            ZipFile.CreateFromDirectory(Path.GetDirectoryName(zipDir), destinationFileName);

            return(destinationFileName);
        }