// ==========================================================
//  MIXBOX 2.0 (c) 2022 Secret Weapons. All rights reserved.
//  License: Creative Commons Attribution-NonCommercial 4.0
//  Authors: Sarka Sochorova and Ondrej Jamriska
// ==========================================================
//
//   BASIC USAGE
//
//      int colorMix = Mixbox.Lerp(color1, color2, t);
//
//   MULTI-COLOR MIXING
//
//      MixboxLatent z1 = Mixbox.RGBToLatent(color1);
//      MixboxLatent z2 = Mixbox.RGBToLatent(color2);
//      MixboxLatent z3 = Mixbox.RGBToLatent(color3);
//
//      MixboxLatent zMix = (0.3f*z1 + // 30% of color1
//                           0.6f*z2 + // 60% of color2
//                           0.1f*z3); // 10% of color3
//
//      int colorMix = Mixbox.LatentToRGB(zMix);
//
//   PIGMENT COLORS
//
//      Cadmium Yellow                    254, 236,   0
//      Hansa Yellow                      252, 211,   0
//      Cadmium Orange                    255, 105,   0
//      Cadmium Red                       255,  39,   2
//      Quinacridone Magenta              128,   2,  46
//      Cobalt Violet                      78,   0,  66
//      Ultramarine Blue                   25,   0,  89
//      Cobalt Blue                         0,  33, 133
//      Phthalo Blue                       13,  27,  68
//      Phthalo Green                       0,  60,  50
//      Permanent Green                     7, 109,  22
//      Sap Green                         107, 148,   4
//      Burnt Sienna                      123,  72,   0
//
//   LICENSING
//
//      If you want to obtain commercial license, please
//      contact us at: mixbox@scrtwpns.com
//

using System;
using System.Globalization;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace Scrtwpns.Mixbox
{
    public static class Mixbox
    {
        public static int Lerp(int color1, int color2, float t)
        {
            int colorMix = LatentToRGB((1.0f-t)*RGBToLatent(color1) + t*RGBToLatent(color2));

            float alpha1 = (color1 >> 24) & 0xFF;
            float alpha2 = (color2 >> 24) & 0xFF;
            int alphaMix = Clamp0255((int)Math.Round((1.0f-t)*alpha1 + t*alpha2));

            return (alphaMix << 24) | (colorMix & 0xFFFFFF);
        }

        public static int[] Lerp(int[] color1, int[] color2, float t)
        {
            int colorMix = LatentToRGB((1.0f-t)*RGBToLatent(color1) + t*RGBToLatent(color2));

            if (color1.Length == 3 && color2.Length == 3)
            {
                return new int[] { (colorMix >> 16) & 0xFF, (colorMix >> 8) & 0xFF, colorMix & 0xFF };
            }

            int alpha1 = color1.Length > 3 ? color1[3] : 255;
            int alpha2 = color2.Length > 3 ? color2[3] : 255;
            int alphaMix = Clamp0255((int)Math.Round((1.0f-t)*alpha1 + t*alpha2));

            return new int[] { (colorMix >> 16) & 0xFF, (colorMix >> 8) & 0xFF, colorMix & 0xFF, alphaMix };
        }

        public static float[] LerpFloat(float[] color1, float[] color2, float t)
        {
            float[] colorMix = LatentToFloatRGB((1.0f-t)*FloatRGBToLatent(color1) + t*FloatRGBToLatent(color2));

            if (color1.Length == 3 && color2.Length == 3) return colorMix;

            float alpha1 = color1.Length > 3 ? color1[3] : 1.0f;
            float alpha2 = color2.Length > 3 ? color2[3] : 1.0f;
            float alphaMix = (1.0f-t)*alpha1 + t*alpha2;

            return new float[] { colorMix[0], colorMix[1], colorMix[2], alphaMix };
        }

        public static float[] LerpLinearFloat(float[] color1, float[] color2, float t)
        {
            float[] colorMix = LatentToLinearFloatRGB((1.0f-t)*LinearFloatRGBToLatent(color1) + t*LinearFloatRGBToLatent(color2));

            if (color1.Length == 3 && color2.Length == 3) return colorMix;

            float alpha1 = color1.Length > 3 ? color1[3] : 1.0f;
            float alpha2 = color2.Length > 3 ? color2[3] : 1.0f;
            float alphaMix = (1.0f-t)*alpha1 + t*alpha2;

            return new float[] { colorMix[0], colorMix[1], colorMix[2], alphaMix };
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static MixboxLatent RGBToLatent(int r, int g, int b)
        {
            return FloatRGBToLatent(((float)r) / 255.0f, ((float)g) / 255.0f, ((float)b) / 255.0f);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static MixboxLatent RGBToLatent(int[] rgb)
        {
            return RGBToLatent(rgb[0], rgb[1], rgb[2]);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static MixboxLatent RGBToLatent(int color)
        {
            return RGBToLatent((color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int LatentToRGB(MixboxLatent latent)
        {
            float[] rgb = EvalPolynomial(latent.c0, latent.c1, latent.c2, latent.c3);
            return (int)(0xFF000000 |
                        (((uint)Math.Round(Clamp01(rgb[0] + latent.rR) * 255.0f)) << 16) |
                        (((uint)Math.Round(Clamp01(rgb[1] + latent.rG) * 255.0f)) <<  8) |
                        (((uint)Math.Round(Clamp01(rgb[2] + latent.rB) * 255.0f))      ));
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static MixboxLatent FloatRGBToLatent(float r, float g, float b)
        {
            r = Clamp01(r);
            g = Clamp01(g);
            b = Clamp01(b);

            float x = r * 63.0f;
            float y = g * 63.0f;
            float z = b * 63.0f;

            int ix = (int)x;
            int iy = (int)y;
            int iz = (int)z;

            float tx = x - (float)ix;
            float ty = y - (float)iy;
            float tz = z - (float)iz;

            int xyz = (ix + iy * 64 + iz * 64 * 64) & 0x3FFFF;

            float c0 = 0.0f;
            float c1 = 0.0f;
            float c2 = 0.0f;

            float w = 0.0f;
            w = (1.0f-tx)*(1.0f-ty)*(1.0f-tz); c0 += w*Lut[xyz+ 192]; c1 += w*Lut[xyz+262336]; c2 += w*Lut[xyz+524480];
            w = (     tx)*(1.0f-ty)*(1.0f-tz); c0 += w*Lut[xyz+ 193]; c1 += w*Lut[xyz+262337]; c2 += w*Lut[xyz+524481];
            w = (1.0f-tx)*(     ty)*(1.0f-tz); c0 += w*Lut[xyz+ 256]; c1 += w*Lut[xyz+262400]; c2 += w*Lut[xyz+524544];
            w = (     tx)*(     ty)*(1.0f-tz); c0 += w*Lut[xyz+ 257]; c1 += w*Lut[xyz+262401]; c2 += w*Lut[xyz+524545];
            w = (1.0f-tx)*(1.0f-ty)*(     tz); c0 += w*Lut[xyz+4288]; c1 += w*Lut[xyz+266432]; c2 += w*Lut[xyz+528576];
            w = (     tx)*(1.0f-ty)*(     tz); c0 += w*Lut[xyz+4289]; c1 += w*Lut[xyz+266433]; c2 += w*Lut[xyz+528577];
            w = (1.0f-tx)*(     ty)*(     tz); c0 += w*Lut[xyz+4352]; c1 += w*Lut[xyz+266496]; c2 += w*Lut[xyz+528640];
            w = (     tx)*(     ty)*(     tz); c0 += w*Lut[xyz+4353]; c1 += w*Lut[xyz+266497]; c2 += w*Lut[xyz+528641];

            c0 /= 255.0f;
            c1 /= 255.0f;
            c2 /= 255.0f;

            float c3 = 1.0f - (c0 + c1 + c2);

            float[] rgbMix = EvalPolynomial(c0, c1, c2, c3);

            return new MixboxLatent(
                c0,
                c1,
                c2,
                c3,
                r - rgbMix[0],
                g - rgbMix[1],
                b - rgbMix[2]
            );
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static MixboxLatent FloatRGBToLatent(float[] rgb)
        {
            return FloatRGBToLatent(rgb[0], rgb[1], rgb[2]);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static float[] LatentToFloatRGB(MixboxLatent latent)
        {
            float[] rgb = EvalPolynomial(latent.c0, latent.c1, latent.c2, latent.c3);
            return new float[]
            {
                Clamp01(rgb[0] + latent.rR),
                Clamp01(rgb[1] + latent.rG),
                Clamp01(rgb[2] + latent.rB),
            };
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static MixboxLatent LinearFloatRGBToLatent(float r, float g, float b)
        {
            return FloatRGBToLatent(LinearToSRGB(r),
                                    LinearToSRGB(g),
                                    LinearToSRGB(b));
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static MixboxLatent LinearFloatRGBToLatent(float[] rgb)
        {
            return LinearFloatRGBToLatent(rgb[0], rgb[1], rgb[2]);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static float[] LatentToLinearFloatRGB(MixboxLatent latent)
        {
            float[] rgb = LatentToFloatRGB(latent);
            return new float[]
            {
                SRGBToLinear(rgb[0]),
                SRGBToLinear(rgb[1]),
                SRGBToLinear(rgb[2]),
            };
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static float Clamp01(float x)
        {
            return x < 0.0f ? 0.0f : x > 1.0f ? 1.0f : x;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static int Clamp0255(int x)
        {
            return x < 0 ? 0 : x > 255 ? 255 : x;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static float SRGBToLinear(float x)
        {
            return (x >= 0.04045f) ? (float)Math.Pow((x + 0.055f) / 1.055f, 2.4f) : x / 12.92f;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static float LinearToSRGB(float x)
        {
            return (x >= 0.0031308f) ? 1.055f * ((float)Math.Pow(x,1.0f / 2.4f)) - 0.055f : 12.92f * x;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static float[] EvalPolynomial(float c0, float c1, float c2, float c3)
        {
            float r = 0.0f;
            float g = 0.0f;
            float b = 0.0f;

            float c00 = c0 * c0;
            float c11 = c1 * c1;
            float c22 = c2 * c2;
            float c33 = c3 * c3;
            float c01 = c0 * c1;
            float c02 = c0 * c2;
            float c12 = c1 * c2;

            float w = 0.0f;
            w = c0 * c00; r += +0.07717053f * w; g += +0.02826978f * w; b += +0.24832992f * w;
            w = c1 * c11; r += +0.95912302f * w; g += +0.80256528f * w; b += +0.03561839f * w;
            w = c2 * c22; r += +0.74683774f * w; g += +0.04868586f * w; b += +0.00000000f * w;
            w = c3 * c33; r += +0.99518138f * w; g += +0.99978149f * w; b += +0.99704802f * w;
            w = c00 * c1; r += +0.04819146f * w; g += +0.83363781f * w; b += +0.32515377f * w;
            w = c01 * c1; r += -0.68146950f * w; g += +1.46107803f * w; b += +1.06980936f * w;
            w = c00 * c2; r += +0.27058419f * w; g += -0.15324870f * w; b += +1.98735057f * w;
            w = c02 * c2; r += +0.80478189f * w; g += +0.67093710f * w; b += +0.18424500f * w;
            w = c00 * c3; r += -0.35031003f * w; g += +1.37855826f * w; b += +3.68865000f * w;
            w = c0 * c33; r += +1.05128046f * w; g += +1.97815239f * w; b += +2.82989073f * w;
            w = c11 * c2; r += +3.21607125f * w; g += +0.81270228f * w; b += +1.03384539f * w;
            w = c1 * c22; r += +2.78893374f * w; g += +0.41565549f * w; b += -0.04487295f * w;
            w = c11 * c3; r += +3.02162577f * w; g += +2.55374103f * w; b += +0.32766114f * w;
            w = c1 * c33; r += +2.95124691f * w; g += +2.81201112f * w; b += +1.17578442f * w;
            w = c22 * c3; r += +2.82677043f * w; g += +0.79933038f * w; b += +1.81715262f * w;
            w = c2 * c33; r += +2.99691099f * w; g += +1.22593053f * w; b += +1.80653661f * w;
            w = c01 * c2; r += +1.87394106f * w; g += +2.05027182f * w; b += -0.29835996f * w;
            w = c01 * c3; r += +2.56609566f * w; g += +7.03428198f * w; b += +0.62575374f * w;
            w = c02 * c3; r += +4.08329484f * w; g += -1.40408358f * w; b += +2.14995522f * w;
            w = c12 * c3; r += +6.00078678f * w; g += +2.55552042f * w; b += +1.90739502f * w;

            return new float[] { r, g, b };
        }

        private static readonly byte[] Lut;

        static Mixbox()
        {
            Lut = ZlibDecoder.Inflate(Convert.FromBase64String("[encoded data, 151148 characters]")).ToArray();

            for (int i = 0; i < Lut.Length; i++)
            {
                Lut[i] = (byte)((((i & 63) != 0) ? Lut[i - 1] : 127) + (Lut[i] - 127));
            }
        }
    }

    public partial struct MixboxLatent : IEquatable<MixboxLatent>, IFormattable
    {
        public float c0;
        public float c1;
        public float c2;
        public float c3;
        public float rR;
        public float rG;
        public float rB;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public MixboxLatent(float c0, float c1, float c2, float c3, float rR, float rG, float rB)
        {
            this.c0 = c0; this.c1 = c1; this.c2 = c2; this.c3 = c3; this.rR = rR; this.rG = rG; this.rB = rB;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static MixboxLatent Lerp(MixboxLatent a, MixboxLatent b, float t)
        {
            return (1.0f-t)*a + t*b;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static MixboxLatent operator+(MixboxLatent a, MixboxLatent b)
        {
            return new MixboxLatent(a.c0 + b.c0, a.c1 + b.c1, a.c2 + b.c2, a.c3 + b.c3, a.rR + b.rR, a.rG + b.rG, a.rB + b.rB);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static MixboxLatent operator-(MixboxLatent a, MixboxLatent b)
        {
            return new MixboxLatent(a.c0 - b.c0, a.c1 - b.c1, a.c2 - b.c2, a.c3 - b.c3, a.rR - b.rR, a.rG - b.rG, a.rB - b.rB);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static MixboxLatent operator-(MixboxLatent a)
        {
            return new MixboxLatent(-a.c0, -a.c1, -a.c2, -a.c3, -a.rR, -a.rG, -a.rB);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static MixboxLatent operator*(MixboxLatent a, float d)
        {
            return new MixboxLatent(a.c0 * d, a.c1 * d, a.c2 * d, a.c3 * d, a.rR * d, a.rG * d, a.rB * d);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static MixboxLatent operator*(float d, MixboxLatent a)
        {
            return new MixboxLatent(a.c0 * d, a.c1 * d, a.c2 * d, a.c3 * d, a.rR * d, a.rG * d, a.rB * d);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static MixboxLatent operator/(MixboxLatent a, float d)
        {
            return new MixboxLatent(a.c0 / d, a.c1 / d, a.c2 / d, a.c3 / d, a.rR / d, a.rG / d, a.rB / d);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public override int GetHashCode()
        {
            return (c0.GetHashCode() ^ (c1.GetHashCode() << 2) ^ (c2.GetHashCode() >> 2) ^ (c3.GetHashCode() >> 1)) ^
                   ((rR.GetHashCode() ^ (rG.GetHashCode() << 2) ^ (rB.GetHashCode() >> 2)) << 2);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public override bool Equals(object other)
        {
            if (!(other is MixboxLatent)) return false;

            return Equals((MixboxLatent)other);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool Equals(MixboxLatent other)
        {
            return c0.Equals(other.c0) && c1.Equals(other.c1) && c2.Equals(other.c2) && c3.Equals(other.c3) &&
                   rR.Equals(other.rR) && rG.Equals(other.rG) && rB.Equals(other.rB);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public override string ToString()
        {
            return ToString(null, null);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public string ToString(string format)
        {
            return ToString(format, null);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public string ToString(string format, IFormatProvider formatProvider)
        {
            if (string.IsNullOrEmpty(format)) { format = "F2"; }
            if (formatProvider == null) { formatProvider = CultureInfo.InvariantCulture.NumberFormat; }
            return String.Format(
                CultureInfo.InvariantCulture.NumberFormat,
                "({0}, {1}, {2}, {3}, {4}, {5}, {6})",
                c0.ToString(format, formatProvider),
                c1.ToString(format, formatProvider),
                c2.ToString(format, formatProvider),
                c3.ToString(format, formatProvider),
                rR.ToString(format, formatProvider),
                rG.ToString(format, formatProvider),
                rB.ToString(format, formatProvider)
            );
        }
    }

    internal class ZlibDecoder
    {
        public static List<byte> Inflate(IList<byte> compressed)
        {
            return new ZlibDecoder { input = compressed }.Inflate();
        }

        private const int fastBits = 9;
        private const int fastMask = ((1 << fastBits) - 1);

        private static readonly int[] DistExtra = new[]
        {
            0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9,
            10, 10, 11, 11, 12, 12, 13, 13
        };

        private static readonly int[] LengthBase = new[]
        {
            3, 4, 5, 6, 7, 8, 9, 10, 11, 13,
            15, 17, 19, 23, 27, 31, 35, 43, 51, 59,
            67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0
        };

        private static readonly int[] LengthExtra = new[]
        {
            0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4,
            4, 4, 4, 5, 5, 5, 5, 0, 0, 0
        };

        private static readonly int[] DistBase = new[]
        {
            1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
            257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193,
            12289, 16385, 24577, 0, 0
        };

        private static readonly int[] LengthDezigzag = new[]
        {
            16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
        };

        private List<byte> output;
        private UInt32 codeBuffer;
        private int numBits;

        private Huffman distance;
        private Huffman length;

        private int inPos;
        private IList<byte> input;

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static int BitReverse16(int n)
        {
            n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1);
            n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2);
            n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4);
            n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8);
            return n;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static int BitReverse(int v, int bits)
        {
            return BitReverse16(v) >> (16 - bits);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private int Get8()
        {
            return inPos >= input.Count ? 0 : input[inPos++];
        }

        private void FillBits()
        {
            do
            {
                codeBuffer |= (uint)(Get8() << numBits);
                numBits += 8;
            } while (numBits <= 24);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private uint Receive(int n)
        {
            if (numBits < n) FillBits();
            var k = (uint)(codeBuffer & ((1 << n) - 1));
            codeBuffer >>= n;
            numBits -= n;
            return k;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private int HuffmanDecode(Huffman z)
        {
            int s;
            if (numBits < 16) FillBits();
            int b = z.Fast[codeBuffer & fastMask];
            if (b < 0xFFFF)
            {
                s = z.Size[b];
                codeBuffer >>= s;
                numBits -= s;
                return z.Value[b];
            }

            int k = BitReverse((int) codeBuffer, 16);
            for (s = fastBits + 1;; ++s)
            {
                if (k < z.MaxCode[s]) break;
            }
            if (s == 16) return -1;

            b = (k >> (16 - s)) - z.FirstCode[s] + z.FirstSymbol[s];

            codeBuffer >>= s;
            numBits -= s;
            return z.Value[b];
        }

        private void ParseHuffmanBlock()
        {
            for (;;)
            {
                int z = HuffmanDecode(length);
                if (z < 256)
                {
                    output.Add((byte)z);
                }
                else
                {
                    if (z == 256) return;
                    z -= 257;
                    int len = LengthBase[z];
                    if (LengthExtra[z] != 0) { len += (int)Receive(LengthExtra[z]); }
                    z = HuffmanDecode(distance);
                    int dist = DistBase[z];
                    if (DistExtra[z] != 0) { dist += (int)Receive(DistExtra[z]); }
                    dist = output.Count - dist;
                    for (int i = 0; i < len; i++, dist++) { output.Add(output[dist]); }
                }
            }
        }

        private void ComputeHuffmanCodes()
        {
            var lenCodes = new byte[286 + 32 + 137];
            var codeLengthSizes = new byte[19];

            uint hlit = Receive(5) + 257;
            uint hdist = Receive(5) + 1;
            uint hclen = Receive(4) + 4;

            for (int i = 0; i < hclen; i++)
            {
                codeLengthSizes[LengthDezigzag[i]] = (byte)Receive(3);
            }

            var codeLength = new Huffman(new ArraySegment<byte>(codeLengthSizes));

            int n = 0;
            while (n < hlit + hdist)
            {
                int c = HuffmanDecode(codeLength);
                if (c < 16)
                {
                    lenCodes[n++] = (byte)c;
                }
                else if (c == 16)
                {
                    c = (int)Receive(2) + 3;
                    for (int i = 0; i < c; i++) lenCodes[n + i] = lenCodes[n - 1];
                    n += c;
                }
                else if (c == 17)
                {
                    c = (int)Receive(3) + 3;
                    for (int i = 0; i < c; i++) lenCodes[n + i] = 0;
                    n += c;
                }
                else
                {
                    c = (int)Receive(7) + 11;
                    for (int i = 0; i < c; i++) lenCodes[n + i] = 0;
                    n += c;
                }
            }

            length = new Huffman(new ArraySegment<byte>(lenCodes, 0, (int)hlit));
            distance = new Huffman(new ArraySegment<byte>(lenCodes, (int)hlit, (int)hdist));
        }

        private List<byte> Inflate()
        {
            output = new List<byte>();
            numBits = 0;
            codeBuffer = 0;

            bool final;
            do
            {
                final = Receive(1) != 0;
                var type = (int)Receive(2);
                ComputeHuffmanCodes();
                ParseHuffmanBlock();
            } while (!final);

            for (int i = 0; i < 4161; i++) { output.Add(0); }

            return output;
        }

        private class Huffman
        {
            public readonly UInt16[] Fast = new UInt16[1 << fastBits];
            public readonly UInt16[] FirstCode = new UInt16[16];
            public readonly UInt16[] FirstSymbol = new UInt16[16];
            public readonly int[] MaxCode = new int[17];
            public readonly Byte[] Size = new Byte[288];
            public readonly UInt16[] Value = new UInt16[288];

            public Huffman(ArraySegment<byte> sizeList)
            {
                var nextCode = new int[16];
                var sizes = new int[17];

                for (int i = 0; i < Fast.Length; i++) { Fast[i] = 0xFFFF; }
                for (int i = 0; i < sizeList.Count; i++) { ++sizes[sizeList.Array[i + sizeList.Offset]]; }

                sizes[0] = 0;
                int code = 0;
                int k = 0;
                for (int i = 1; i < 16; i++)
                {
                    nextCode[i] = code;
                    FirstCode[i] = (UInt16)code;
                    FirstSymbol[i] = (UInt16)k;
                    code = (code + sizes[i]);
                    MaxCode[i] = code << (16 - i);
                    code <<= 1;
                    k += sizes[i];
                }
                MaxCode[16] = 0x10000;

                for (int i = 0; i < sizeList.Count; i++)
                {
                    int s = sizeList.Array[i + sizeList.Offset];
                    if (s != 0)
                    {
                        int c = nextCode[s] - FirstCode[s] + FirstSymbol[s];
                        Size[c] = (byte)s;
                        Value[c] = (UInt16)i;
                        if (s <= fastBits)
                        {
                            int j = BitReverse(nextCode[s], s);
                            while (j < (1 << fastBits))
                            {
                                Fast[j] = (UInt16)c;
                                j += (1 << s);
                            }
                        }
                        ++nextCode[s];
                    }
                }
            }
        }
    }
}
