/// <summary>
        /// Initializes a new instance of the WaveInventoryCreator class.
        /// </summary>
        /// <param name="resultFile">The file name of the result files without extension name.</param>
        /// <param name="header">The wave info header.</param>
        /// <param name="namedUnitTypeId">The Dictionary which key is unit name and the value is index type id.</param>
        /// <param name="phoneIdMap">Phone id map .</param>
        /// <param name="millisecondPerFrame">Millisecond per frame.</param>
        /// <param name="keepLeftRightFrameMargin">Keep left right frame margin.</param>
        /// <param name="obfuscationMethod">The given method to perform obfuscation for the wave data.</param>
        public WaveInventoryCreator(string resultFile,
            WaveInfoHeader header,
            IDictionary<string, int> namedUnitTypeId,
            IDictionary<string, int> phoneIdMap,
            int millisecondPerFrame,
            int keepLeftRightFrameMargin,
            ObfuscationMethod obfuscationMethod)
        {
            if (string.IsNullOrEmpty(resultFile))
            {
                throw new ArgumentNullException("resultFile");
            }

            if (header == null)
            {
                throw new ArgumentNullException("header");
            }

            if (namedUnitTypeId == null)
            {
                throw new ArgumentNullException("namedUnitTypeId");
            }

            if (phoneIdMap == null)
            {
                throw new ArgumentNullException("phoneIdMap");
            }

            if (obfuscationMethod == null)
            {
                throw new ArgumentNullException("obfuscationMethod");
            }

            if (millisecondPerFrame <= 0)
            {
                throw new ArgumentException("millisecondPerFrame");
            }

            if (keepLeftRightFrameMargin < 0)
            {
                throw new ArgumentException("keepLeftRightFrameMargin");
            }

            _obfuscationMethod = obfuscationMethod;
            _header = header;
            _header.Validate();
            _ccMarginLength = (int)(_header.CrossCorrelationMarginLength * 0.001f * _header.SamplesPerSecond);
            _fsMarginLength = (int)(keepLeftRightFrameMargin * millisecondPerFrame * 0.001f * _header.SamplesPerSecond);

            _namedUnitTypeId = namedUnitTypeId;
            _phoneIdMap = phoneIdMap;
            _indexingFile = new UnitIndexingFile(namedUnitTypeId);
            _fileName = resultFile;
            Helper.EnsureFolderExistForFile(_fileName);

            string fullFileName;
            switch (_header.Compression)
            {
                case WaveCompressCatalog.Unc:
                    fullFileName = _fileName.AppendExtensionName(FileExtensions.WaveInventory);
                    break;
                default:
                    throw new NotSupportedException("Only Unc is supported");
            }

            _fileStream = new FileStream(fullFileName, FileMode.Create);
            _writer = new BinaryWriter(_fileStream);

            _voiceFontHeader = new VoiceFontHeader
            {
                FileTag = 0x45564157,   // "WAVE"
                FormatTag = VoiceFontTag.FmtIdUncompressedWaveInventory,
                Version = 0,
                Build = 0,
                DataSize = 0,
            };

            _voiceFontHeader.Save(_writer);
            _dataOffset = _writer.BaseStream.Position;
            _millisecondPerFrame = millisecondPerFrame;

            LogFile = string.Empty;
        }
        /// <summary>
        /// Update the ACD file.
        /// When we split the WVE, if the sub WVE data can't be divided exactly using a frame data size. 
        /// To keep consistent with compressed font, we need add some data into the sub WVE.
        /// After this operation, we need update the frame index in UNT, all freame index behind of the added frame will be increase some frame offset.
        /// However, when we get the frame information in ACD, we need using the frame index in unt file. 
        /// So to keep consistent, we need insert some frame in ACD file the same time.
        /// </summary>
        /// <param name="indexFile">Unit index data.</param>
        /// <param name="sentencFrameUpdateList">Frame update list.</param>
        /// <param name="acdFilePath">Old acd file path.</param>
        /// <param name="newAcdFilePath">New acd file path.</param>
        private static void UpdateACDFile(UnitIndexingFile indexFile, Dictionary<string, uint> sentencFrameUpdateList, string acdFilePath, string newAcdFilePath)
        {
            if (sentencFrameUpdateList.Where(k => k.Value != 0).ToList().Count != 0 && !File.Exists(acdFilePath))
            {
                PrintLog(string.Format("Please check your font, the ACD file need to be update!"), true);
            }

            if (File.Exists(acdFilePath))
            {
                ACDData acdData = null;
                using (FileStream acdFile = File.Open(acdFilePath, FileMode.Open))
                using (ACDDataReader acdReader = new ACDDataReader(new BinaryReader(acdFile)))
                {
                    acdData = acdReader.ReadACDData();
                }

                foreach (var item in sentencFrameUpdateList)
                {
                    // Get all candidate which are got from the specfied sentenc and sorted by frame index.
                    List<WaveCandidateInfo> endSenteceWaveInfo = indexFile.WaveCandidates.SelectMany(c => c.Value.Where(p => p.Value.SentenceId == item.Key))
                                                                                                 .Select(i => i.Value).SortBy(k => k.FrameIndexInSentence).ToList();

                    // Get the end candiate in the specifid sentence.
                    WaveCandidateInfo firstWaveCandidate = endSenteceWaveInfo[0];
                    uint frameStart = (uint)(firstWaveCandidate.FrameIndex - firstWaveCandidate.FrameIndexInSentence);
                    if (frameStart > 0 && acdData != null)
                    {
                        if (acdData.LpccData != null)
                        {
                            AddAcdTableItem(acdData.LpccData, frameStart, item.Value);
                        }

                        if (acdData.RealLpccData != null)
                        {
                            AddAcdTableItem(acdData.RealLpccData, frameStart, item.Value);
                        }

                        if (acdData.F0Data != null)
                        {
                            AddAcdTableItem(acdData.F0Data, frameStart, item.Value);
                        }

                        if (acdData.GainData != null)
                        {
                            AddAcdTableItem(acdData.GainData, frameStart, item.Value);
                        }

                        if (acdData.PowerData != null)
                        {
                            AddAcdTableItem(acdData.PowerData, frameStart, item.Value);
                        }

                        if (acdData.PitchMarkerData != null)
                        {
                            AddAcdTableItem(acdData.PitchMarkerData, frameStart, item.Value);
                        }
                    }

                    ACDDataWriter writer = new ACDDataWriter();
                    writer.Write(acdData, newAcdFilePath);
                }
            }
        }
 /// <summary>
 /// Update the indexing file based pm tje candiate info.
 /// </summary>
 /// <param name="indexFile">The index file.</param>
 /// <param name="sentenceFrameUpdateList">SentenceOrder list.</param>
 private static void UpdateIndexingFile(UnitIndexingFile indexFile, Dictionary<string, uint> sentenceFrameUpdateList)
 {
     // update all global index.
     indexFile.WaveCandidates.Values.ForEach(p => p.ForEach(w =>
     {
         WaveCandidateInfo wunt = w.Value;
         if (sentenceFrameUpdateList.ContainsKey(wunt.SentenceId))
         {
             wunt.FrameIndex += sentenceFrameUpdateList[wunt.SentenceId];
         }
         else
         {
             throw new Exception(string.Format("Please check the code, some sentence missed. senteceID {0}", wunt.SentenceId));
         }
     }));
 }
        /// <summary>
        /// Start the pruning.
        /// </summary>
        /// <param name="fontPath">Voice font path, like .\1033 .</param>
        /// <param name="configFile">Config file which will tell how many sentence will be include in each sub wve.</param>
        /// <param name="bFillData">Whether fill data if the sub WVE can't be exact division.</param>
        /// <param name="outputFolder">Target directory.</param>
        /// <returns>New WVE File path List.</returns>
        public static List<string> InventorySplit(string fontPath, string configFile, bool bFillData, string outputFolder)
        {
            string unitfile = fontPath + ".unt";
            string wihFile = fontPath + ".wih";
            string wveFile = fontPath + ".wve";
            string outputUnit = Path.Combine(outputFolder, Path.GetFileNameWithoutExtension(unitfile) + ".Splited.unt");

            PrintLog("Start to update split the WVE file...", true);

            // spliet the file.
            List<string> newWVEFileList = new List<string>();

            // Load the WIH file.
            WaveInfoHeader header = new WaveInfoHeader();
            header.Load(wihFile);
            const uint CompressFrameSize = 640;

            Dictionary<string, uint> untFrameUpdateList = new Dictionary<string, uint>();
            Dictionary<string, uint> acdFrameUpdateList = new Dictionary<string, uint>();
            uint curFillFrameCount = 0;
            Dictionary<string, int> namedUnitTypeId = new Dictionary<string, int>();
            Dictionary<int, WaveCandidateInfo> updatedCandidates = new Dictionary<int, WaveCandidateInfo>();
            using (UnitIndexingFile indexFile = new UnitIndexingFile(namedUnitTypeId))
            {
                indexFile.Load(unitfile);

                uint bytesPerFrame = indexFile.SamplePerFrame * header.BytesPerSample;
                uint compressFrameCount = CompressFrameSize / bytesPerFrame;

                // Get all sentence id information from the index file canidate, and save them in a sorted dictionary.
                // Because the sentece is sorted by a sorted dictionary in Font traing process.
                List<string> sentencIDs = indexFile.WaveCandidates.SelectMany(c => c.Value.Select(k => k.Value.SentenceId).Distinct()).Distinct().ToList();
                Dictionary<string, int> dic = sentencIDs.Select((v, i) => new { Value = v, Index = i }).ToDictionary(p => p.Value, p => p.Index);
                SortedDictionary<string, int> sortedSentenceDic = new SortedDictionary<string, int>(dic);
                List<string> orderedSentenceIDs = sortedSentenceDic.Select(e => e.Key).ToList();

                // Get the confing data.
                List<uint> sentencCountList = ReadConfigFile(configFile, (uint)orderedSentenceIDs.Count);

                using (FileStream readerStream = File.Open(wveFile, FileMode.Open))
                {
                    BinaryReader reader = new BinaryReader(readerStream);

                    // load header
                    VoiceFontHeader fontHeader = new VoiceFontHeader();
                    fontHeader.Load(reader);

                    PrintLog("[Totoal Frame in each new VWF file.]", true);

                    // the data offset from the header
                    long dataOffSet = reader.BaseStream.Position;
                    uint outputFileSuffix = 0;
                    uint readFrameNumber = 0;
                    int curSplitSegmentIndex = 0;
                    for (int index = 0; index <= orderedSentenceIDs.Count; index++)
                    {
                        if (index < sentencCountList[curSplitSegmentIndex])
                        {
                            untFrameUpdateList.Add(orderedSentenceIDs[index], curFillFrameCount);
                            continue;
                        }

                        curSplitSegmentIndex++;
                        string newWVEFileName = Path.Combine(outputFolder, Path.GetFileNameWithoutExtension(wveFile) + "_" + outputFileSuffix.ToString() + ".wve");
                        PrintLog(string.Format("{0} Frame Update Count: {1}", Path.GetFileName(newWVEFileName), curFillFrameCount), true);

                        using (FileStream wveFileStream = File.Open(newWVEFileName, FileMode.Create))
                        {
                            BinaryWriter wveWriter = new BinaryWriter(wveFileStream);
                            fontHeader.Save(wveWriter);
                            long writerOffSet = wveWriter.BaseStream.Position;

                            // calculate lenth
                            long length = 0;
                            uint frameCount = 0;
                            if (index == orderedSentenceIDs.Count)
                            {
                                length = reader.BaseStream.Length - reader.BaseStream.Position;
                                frameCount = (uint)(length / bytesPerFrame);
                            }
                            else
                            {
                                // Get all candidate which are got from the specfied sentenc and sorted by frame index.
                                string endSentenceID = orderedSentenceIDs[index];
                                List<WaveCandidateInfo> endSenteceWaveInfo = indexFile.WaveCandidates.SelectMany(c => c.Value.Where(p => p.Value.SentenceId == endSentenceID))
                                                                                                             .Select(i => i.Value).SortBy(k => k.FrameIndexInSentence).ToList();

                                // Get the end candiate in the specifid sentence.
                                WaveCandidateInfo firstWaveCandidate = endSenteceWaveInfo[0];

                                frameCount = firstWaveCandidate.FrameIndex - firstWaveCandidate.FrameIndexInSentence - readFrameNumber;
                                length = bytesPerFrame * frameCount;
                                readFrameNumber = firstWaveCandidate.FrameIndex - firstWaveCandidate.FrameIndexInSentence;
                            }

                            // we need calculate how many bit will be filled into the WVE when it will be commpressed.
                            // 640 bit will be used in the compress tool, so we will reference this.
                            uint byteGap = (uint)((CompressFrameSize - (length % CompressFrameSize)) % CompressFrameSize);
                            uint updateFrameCount = (byteGap / bytesPerFrame) % compressFrameCount;
                            curFillFrameCount += updateFrameCount;

                            reader.BaseStream.Seek(dataOffSet, SeekOrigin.Begin);
                            byte[] data = reader.ReadBytes((int)length);
                            wveWriter.Write(data);

                            // fill data if the length can't be exact division.
                            if (bFillData)
                            {
                                FillSilenceData(wveWriter, byteGap);
                            }

                            // save the header file
                            fontHeader.DataSize = (ulong)(wveWriter.BaseStream.Position - writerOffSet);
                            SaveFontHeader(fontHeader, wveWriter);

                            dataOffSet = reader.BaseStream.Position;
                            PrintLog(string.Format("{0} Total Frame Count: {1} + {2}", Path.GetFileName(newWVEFileName), frameCount, updateFrameCount.ToString()), true);
                            PrintLog(string.Empty, true);

                            // recode the new file path.
                            newWVEFileList.Add(newWVEFileName);

                            if (index != orderedSentenceIDs.Count)
                            {
                                acdFrameUpdateList.Add(orderedSentenceIDs[index], curFillFrameCount);
                            }
                        }

                        header.Save(Path.ChangeExtension(newWVEFileName, "wih"));
                        outputFileSuffix++;

                        if (index != orderedSentenceIDs.Count)
                        {
                            index--;
                        }
                    }
                }

                // update ACD file.
                PrintLog("Update ACD file!", true);
                UpdateACDFile(indexFile, acdFrameUpdateList, fontPath + ".acd", Path.ChangeExtension(outputUnit, "acd"));

                // update index file
                PrintLog("Update UNT file!", true);
                UpdateIndexingFile(indexFile, untFrameUpdateList);
                indexFile.Save(outputUnit);
            }

            PrintLog("Split successfully!", true);

            return newWVEFileList;
        }
        /// <summary>
        /// Compares two UNT files by ignoring builder number.
        /// </summary>
        /// <param name="left">The left file name of UNT file.</param>
        /// <param name="right">The right file name of UNT file.</param>
        /// <returns>True if the two UNT file is equal, otherwise false.</returns>
        public static bool Compare(string left, string right)
        {
            using (UnitIndexingFile leftFile = new UnitIndexingFile(null))
            {
                leftFile.Load(left);
                using (UnitIndexingFile rightFile = new UnitIndexingFile(null))
                {
                    rightFile.Load(right);

                    leftFile.BuildNumber = rightFile.BuildNumber;
                    string tempFile = left + Path.GetRandomFileName();
                    try
                    {
                        leftFile.Save(tempFile);
                        return Helper.CompareBinary(tempFile, right);
                    }
                    finally
                    {
                        Helper.SafeDelete(tempFile);
                    }
                }
            }
        }
        /// <summary>
        /// Create domain index file.
        /// </summary>
        /// <param name="scriptFile">Script file.</param>
        /// <param name="domainList">Domain list.</param>
        /// <param name="uif">Name indexed unit features.</param>
        public void Create(XmlScriptFile scriptFile, DomainConfigList domainList, UnitIndexingFile uif)
        {
            // Parameters Validation
            if (scriptFile == null)
            {
                throw new ArgumentNullException("scriptFile");
            }

            if (domainList == null)
            {
                throw new ArgumentNullException("domainList");
            }

            if (uif == null)
            {
                throw new ArgumentNullException("uif");
            }

            Dictionary<string, DomainIndexItem> items =
                new Dictionary<string, DomainIndexItem>(StringComparer.Ordinal);

            _language = scriptFile.Language;
            _tag = domainList.FontTag;
            Phoneme phoneme = Localor.GetPhoneme(_language);
            SliceData sliceData = Localor.GetSliceData(_language);
            foreach (ScriptItem scriptItem in scriptFile.Items)
            {
                if (!domainList.Contains(scriptItem.Id))
                {
                    continue;
                }

                Collection<TtsUnit> itemUnits = scriptItem.GetUnits(phoneme, sliceData);
                Collection<ScriptWord> allPronouncedNormalWords = scriptItem.AllPronouncedNormalWords;
                for (int i = 0; i < allPronouncedNormalWords.Count; i++)
                {
                    ScriptWord word = allPronouncedNormalWords[i];

                    string text;
                    if (domainList.Domain == ScriptDomain.Number)
                    {
                        text = GetNumberDomainWordText(word, scriptItem.Id, i,
                            (domainList as NumberDomainConfigList).Digitals);
                    }
                    else if (domainList.Domain == ScriptDomain.Acronym)
                    {
                        text = GetAcronymDomainWordText(word, scriptItem.Id, i,
                            (domainList as AcronymDomainConfigList).Acronyms);
                    }
                    else if (domainList.Domain == ScriptDomain.Letter)
                    {
                        // Use pronunciation phone ids as key
                        text = GetPhoneIds(word);
                    }
                    else
                    {
                        text = word.Grapheme.ToUpperInvariant();
                    }

                    if (items.ContainsKey(text) &&
                        domainList.Domain != ScriptDomain.Letter)
                    {
                        // Skip duplicate word, except Letter domain
                        continue;
                    }

                    DomainIndexItem item = null;
                    if (!items.ContainsKey(text))
                    {
                        item = new DomainIndexItem();
                        item.Word = text;
                    }
                    else
                    {
                        item = items[text];
                    }

                    bool skipped = false;
                    Collection<TtsUnit> wordUnits = word.GetUnits(phoneme, sliceData);
                    for (int wordUnitIndex = 0; wordUnitIndex < wordUnits.Count; wordUnitIndex++)
                    {
                        TtsUnit unit = wordUnits[wordUnitIndex];
                        FeatureDataItem featureItem = new FeatureDataItem();

                        int indexOfNonSilence = itemUnits.IndexOf(unit);
                        Debug.Assert(indexOfNonSilence >= 0 && indexOfNonSilence < itemUnits.Count);

                        int unitOffset = uif.SearchCandidateOffset(unit.MetaUnit.Name, scriptItem.Id, (uint)indexOfNonSilence);
                        if (unitOffset == -1)
                        {
                            // Skip this word
                            skipped = true;
                            break;
                        }

                        if (item.FeatureItems.Count == wordUnitIndex)
                        {
                            featureItem.UnitIndexes.Add(unitOffset);
                            item.FeatureItems.Add(featureItem); // [].UnitIndexes.Add(unitOffset);
                        }
                        else
                        {
                            item.FeatureItems[wordUnitIndex].UnitIndexes.Add(unitOffset);
                        }
                    }

                    if (!skipped && !items.ContainsKey(item.Word))
                    {
                        items.Add(item.Word, item);
                    }
                }
            }

            _items = BuildHashTable(items.Values);
        }