protected override (MetaBlock, BrotliGlobalState) Transform(MetaBlock.Compressed original, BrotliGlobalState state, BrotliCompressionParameters parameters)
        {
            var builder = new CompressedMetaBlockBuilder(original, state)
            {
                LiteralCtxMap  = ContextMapBuilder.Literals.Simple,
                DistanceCtxMap = ContextMapBuilder.Distances.Simple
            };

            foreach (var category in Categories.LID)
            {
                builder.BlockTypes[category].Reset();
            }

            builder.UseSameLiteralContextMode(LiteralContextMode.UTF8);

            var tracker = new MetaBlockSizeTracker(state);

            tracker.Test(builder, parameters);

            foreach (var category in Categories.LID)
            {
                TestBlockSplits(builder, parameters, tracker, category);
            }

            tracker.Test(original);
            return(tracker.Smallest ?? throw new InvalidOperationException("Transformation did not generate any meta-blocks."));
        }
        protected override (MetaBlock, BrotliGlobalState) Transform(MetaBlock.Compressed original, BrotliGlobalState state, BrotliCompressionParameters parameters)
        {
            var builder = new CompressedMetaBlockBuilder(original, state);
            var tracker = new MetaBlockSizeTracker(state);

            for (byte postfixBitCount = 0; postfixBitCount <= DistanceParameters.MaxPostfixBitCount; postfixBitCount++)
            {
                for (byte directCodeBits = 0; directCodeBits <= DistanceParameters.MaxDirectCodeBits; directCodeBits++)
                {
                    builder.DistanceParameters = new DistanceParameters(postfixBitCount, directCodeBits);
                    tracker.Test(builder, parameters, debugText: "[PostfixBitCount = " + postfixBitCount + ", DirectCodeBits = " + directCodeBits + "]");
                }
            }

            return(tracker.Smallest ?? throw new InvalidOperationException("Transformation did not generate any meta-blocks."));
        }
        protected override (MetaBlock, BrotliGlobalState) Transform(MetaBlock.Compressed original, BrotliGlobalState state, BrotliCompressionParameters parameters)
        {
            var builder = new CompressedMetaBlockBuilder(original, state);
            var tracker = new MetaBlockSizeTracker(state);

            tracker.Test(builder, parameters);

            var blocker = new Blocker(parameters.DistanceCodePicker);

            parameters = new BrotliCompressionParameters.Builder(parameters)
            {
                DistanceCodePicker = blocker.Pick
            }.Build();

            foreach (var code in DistanceCode.Last.Codes.Except(new DistanceCode[] { DistanceCode.Zero }))
            {
                var prev = tracker.SmallestSize;

                blocker.BlockedCodes.Add(code);
                tracker.Test(builder, parameters);

                if (tracker.SmallestSize < prev)
                {
                    Debug.WriteLine("Blocking code " + code + " reduced size (" + prev + " > " + tracker.SmallestSize + "), keeping it...");
                }
                else
                {
                    Debug.WriteLine("Blocking code " + code + " did not improve the size, continuing...");
                    blocker.BlockedCodes.Remove(code);
                }
            }

            Debug.WriteLine("Final blocked codes: " + string.Join(", ", blocker.BlockedCodes));

            return(tracker.Smallest ?? throw new InvalidOperationException("Transformation did not generate any meta-blocks."));
        }
        private void TestBlockSplits(CompressedMetaBlockBuilder builder, BrotliCompressionParameters parameters, MetaBlockSizeTracker tracker, Category category)
        {
            int totalBlockLength = builder.GetTotalBlockLength(category);

            if (totalBlockLength == 0)
            {
                return;
            }

            int step      = Math.Max(1, (int)Math.Floor(Math.Log(totalBlockLength, 1.15)));
            int stepTwice = step * 2;

            if (totalBlockLength < stepTwice)
            {
                return;
            }

            var lengths = new List <int> {
                totalBlockLength
            };
            var queue = new Queue <int>();

            queue.Enqueue(0);

            while (queue.TryDequeue(out int leftIndex))
            {
                if (lengths[leftIndex] < stepTwice)
                {
                    continue;
                }

                int rightIndex = leftIndex + 1;

                lengths.Insert(rightIndex, lengths[leftIndex] - step);
                lengths[leftIndex] = step;

                ApplyBlockSplit(builder.BlockTypes[category], lengths);
                PrepareContextMap(builder, category, lengths.Count);
                tracker.Test(builder, parameters);

                while (lengths[leftIndex] + step < lengths[rightIndex] && lengths[rightIndex] >= stepTwice)
                {
                    lengths[leftIndex]  += step;
                    lengths[rightIndex] -= step;

                    ApplyBlockSplit(builder.BlockTypes[category], lengths);
                    PrepareContextMap(builder, category, lengths.Count);
                    tracker.Test(builder, parameters);
                }

                var smallest = tracker.Smallest;

                if (smallest == null)
                {
                    return;
                }

                var mb   = smallest.Value.Item1;
                int prev = lengths.Count;

                lengths.Clear();
                lengths.Add(mb.Header.BlockTypes[category].InitialLength);
                lengths.AddRange(mb.Data.BlockSwitchCommands[category].Select(command => command.Length));

                var finalLength = totalBlockLength - lengths.Sum();

                if (finalLength > 0)
                {
                    lengths.Add(totalBlockLength - lengths.Sum());
                }

                if (lengths.Count >= prev)
                {
                    queue.Enqueue(leftIndex);
                    queue.Enqueue(rightIndex);
                }
            }
        }