/// <summary>
        /// Shift all lines in "list to change" by "offset", create a matching, rate the matching and shift lines back.
        /// </summary>
        public double GetRatingOfOffset(double offset, SubtitleMatcher.SubtitleMatcherCache subtitleMatcherParams)
        {
            /*
             * Other ideas for rating:
             *      bonus value for censecutive good bi-match ratings
             */

            // move timings of every line
            subtitleMatcherParams.ShiftTime(offset, false, true);

            // match lines
            var    biMatchedLinesList = SubtitleMatcher.MatchSubtitles(subtitleMatcherParams);
            double finalRating        = 0;

            foreach (var biMatchedLines in biMatchedLinesList)
            {
                double rating = RateBiMatchedLines(biMatchedLines);

                // use rating^3 because that way small values will become smaller
                finalRating += rating * rating * rating;
            }

            // shift timings back
            subtitleMatcherParams.ShiftTime(-offset, false, true);

            return(finalRating);
        }
        /// <summary>
        /// Find best offset for "listToChange" so it matches "referenceList". There can be
        /// multiple "peaks" if one subtitle has additional breaks the other does not have.
        /// Only one of them will be returned.
        /// </summary>
        private double FindBestOffset(RemainingSlice slice)
        {
            var subtitleMatcherParams = SubtitleMatcher.GetParameterCache(slice.referenceListLines, slice.listToChangeLines);

            // the tuple is (offset, rating)
            var firstPassList = new List <OffsetRatingTuple>(5);

            FindGoodOffsets(subtitleMatcherParams, 0, 0.3, 1000, firstPassList);

            // find fine grained offsets around approximated offsets
            var secondPassList = new List <OffsetRatingTuple>(5);

            foreach (var offsetRatingTuple in firstPassList)
            {
                FindGoodOffsets(subtitleMatcherParams, offsetRatingTuple.offset, 0.01, 90, secondPassList);
            }

            return(secondPassList[0].offset);
        }
        /// <summary>
        /// This function will...
        /// a) move all lines to change in slice by offset
        /// b) match the lines in "listToChange" and "referenceList"
        /// c) find a threshold value for matchings
        /// d) find the longest row of consecutive matchings that are above the threshold
        /// e) create slices for lines before and lines after this "good row"
        /// f) reset offset of these remaining lines and put slice into queue
        /// </summary>
        private void ApplyOffset(RemainingSlice slice, double offset, Queue <RemainingSlice> queue)
        {
            UtilsSubtitle.ShiftByTime(slice.listToChangeLines, offset);

            // match lines
            var subtitleMatcherParameters = SubtitleMatcher.GetParameterCache(slice.referenceListLines, slice.listToChangeLines);
            var biMatchedLinesLinkedList  = SubtitleMatcher.MatchSubtitles(subtitleMatcherParameters);
            var biMatchedLinesList        = new List <SubtitleMatcher.BiMatchedLines>(biMatchedLinesLinkedList);

            biMatchedLinesList.Sort(delegate(SubtitleMatcher.BiMatchedLines x, SubtitleMatcher.BiMatchedLines y) {
                return(GetStartTime(x) < GetStartTime(y) ? -1 : 1);
            });


            // --------------------------------------------------
            // find threshold rating
            double averageRating = 0;
            int    numRatings    = 0;

            foreach (var biMatchedLines in biMatchedLinesList)
            {
                double rating = RateBiMatchedLines(biMatchedLines);
                if (rating > 0.0001)                  // ignore "zero" ratings
                {
                    averageRating += rating;
                    numRatings++;
                }
            }
            averageRating /= numRatings;
            double thresholdValue = averageRating * 0.8;

            // --------------------------------------------------
            // Find longest row over threshold rating.
            //
            // Zero ratings may be inbetween good ratings when some
            // lines couldn't get matched (for example street-sign
            // translations that aren't in subtitle file in native language).
            // These are stepped over: There can be a limited number of zero ratings
            // ratings in the row except at the beginning and the end (these will
            // get matched at a different time if possible).
            int numGoodMatched     = 0;
            int currentRowStart    = 0;
            int allowedZeroRatings = 0;


            int maxNumGoodMatched = -1;
            int bestRowStart      = 0;

            for (int index = 0; index < biMatchedLinesList.Count; index++)
            {
                var    biMatchedLines = biMatchedLinesList[index];
                double rating         = RateBiMatchedLines(biMatchedLines);

                if (rating < thresholdValue)
                {
                    // step over zero ratings
                    if (rating > 0.000001 || allowedZeroRatings-- < 0)
                    {
                        numGoodMatched = 0;                         // not a zero rating
                    }
                }
                else
                {
                    // update row start/end
                    if (numGoodMatched == 0)
                    {
                        currentRowStart = index;
                    }
                    numGoodMatched = index - currentRowStart + 1;

                    // save best value
                    if (numGoodMatched > maxNumGoodMatched)
                    {
                        maxNumGoodMatched = numGoodMatched;
                        bestRowStart      = currentRowStart;
                    }

                    allowedZeroRatings = 4;
                }
            }

            // could good row be found?
            if (maxNumGoodMatched == -1)
            {
                return;
            }

            // create new slices left and right
            RemainingSlice newSlice;

            // left slice
            newSlice = GetSubSlice(biMatchedLinesList, 0, bestRowStart);
            //UtilsSubtitle.ShiftByTime(newSlice.listToChangeLines, -offset); // by disabling this code, remaining slices are now embedded in greater slices
            queue.Enqueue(newSlice);

            // right slice
            newSlice = GetSubSlice(biMatchedLinesList, bestRowStart + maxNumGoodMatched + 1, biMatchedLinesList.Count);
            //UtilsSubtitle.ShiftByTime(newSlice.listToChangeLines, -offset);
            queue.Enqueue(newSlice);
        }