public CompressionProcessor()
        {
            _requestHeadersStorage = CompressionInitialHeaders.RequestInitialHeaders;
            _responseHeadersStorage = CompressionInitialHeaders.ResponseInitialHeaders;

            InitCompressor();
            InitDecompressor();
        }
        public CompressionProcessor(ConnectionEnd end)
        {
            if (end == ConnectionEnd.Client)
            {
                _localHeaderTable = CompressionInitialHeaders.ResponseInitialHeaders;
                _remoteHeaderTable = CompressionInitialHeaders.RequestInitialHeaders;
            }
            else
            {
                _localHeaderTable = CompressionInitialHeaders.RequestInitialHeaders;
                _remoteHeaderTable = CompressionInitialHeaders.ResponseInitialHeaders;
            }
            _localRefSet = new SizedHeadersList();
            _remoteRefSet = new SizedHeadersList();

            InitCompressor();
            InitDecompressor();
        }
        //Method retypes as many headers as it can to be Indexed
        //and checks if headers marked as indexed are present in the headers table
        /*private void OptimizeInputAndSendOptimized(List<KeyValuePair<string, string>> headers)
        {
            for (int i = 0; i < headers.Count; i++ )
            {
                var headerKv = new KeyValuePair<string, string>(headers[i].Item1, headers[i].Item2);
                IndexationType headerType = (headers[i].Item3 as Indexation).Type;

                int index = _remoteHeaderTable.IndexOf(headerKv);

                //case headerType == IndexationType.Incremental
                //must not be considered because headers table can contain duplicates
                if (index != -1 && headerType == IndexationType.Substitution)
                {
                    CompressIndexed(headerKv);
                    headers.Remove(headers[i--]);
                }

                //If header marked as indexed, but not found in the table, compress it as incremental.
                if (index == -1 && headerType == IndexationType.Indexed)
                {
                    CompressNonIndexed(headerKv.Key, headerKv.Value, IndexationType.Incremental, 5);
                    headers.Remove(headers[i--]);
                }
            }
        }*/
        public byte[] Compress(HeadersList headers)
        {
            var toSend = new SizedHeadersList();
            var toDelete = new SizedHeadersList(_remoteRefSet);
            ClearStream(_serializerStream, (int) _serializerStream.Position);

            //OptimizeInputAndSendOptimized(headersCopy); - dont need this?

            foreach (var header in headers)
            {
                if (header.Key == null || header.Value == null)
                {
                    throw new InvalidHeaderException(header);
                }
                if (!_remoteRefSet.Contains(header))
                {
                    //Not there, Will send
                    toSend.Add(header);
                }
                else
                {
                    //Already there, don't delete
                    toDelete.Remove(header);
                }
            }
            foreach (var header in toDelete)
            {
                //Anything left in toDelete, should send, so it is deleted from ref set.
                CompressIndexed(header);
                _remoteRefSet.Remove(header); //Update our copy
            }
            foreach (var header in toSend)
            {
                //Send whatever was left in headersCopy
                if (_remoteHeaderTable.Contains(header))
                {
                    CompressIndexed(header);
                }
                else
                {
                    CompressHeader(header, new Indexation(IndexationType.Incremental));
                }
                _remoteRefSet.Add(header); //Update our copy
            }

            _serializerStream.Flush();
            var result = new byte[_serializerStream.Position];
            var streamBuffer = _serializerStream.GetBuffer();
            Buffer.BlockCopy(streamBuffer, 0, result, 0, (int)_serializerStream.Position);
            return result;
        }
        private void ModifyTable(string headerName, string headerValue, IndexationType headerType,
                                        SizedHeadersList useHeadersTable, int index)
        {
            int headerLen = headerName.Length + headerValue.Length;
                switch (headerType)
                {
                    case IndexationType.Incremental:
                        if (useHeadersTable.Count > HeadersLimit - 1)
                        {
                            useHeadersTable.RemoveAt(0);
                        }

                        while (useHeadersTable.StoredHeadersSize + headerLen > MaxHeaderByteSize)
                        {
                            useHeadersTable.RemoveAt(0);
                        }
                        useHeadersTable.Add(new KeyValuePair<string, string>(headerName, headerValue));
                        break;
                    case IndexationType.Substitution:
                        if (index != -1)
                        {
                            useHeadersTable[index] = new KeyValuePair<string, string>(headerName, headerValue);
                        }
                        else
                        {
                            if (useHeadersTable.Count > HeadersLimit - 1)
                            {
                                useHeadersTable.RemoveAt(0);
                            }

                            while (useHeadersTable.StoredHeadersSize + headerLen > MaxHeaderByteSize)
                            {
                                useHeadersTable.RemoveAt(0);
                            }
                            //If header wasn't found then add it to the table
                            useHeadersTable.Add(new KeyValuePair<string, string>(headerName, headerValue));
                        }
                        break;
                    default:
                        return;
                }
        }
        public HeadersList Decompress(byte[] serializedHeaders)
        {
            try
            {
                var workingSet = new SizedHeadersList(_localRefSet);

                _currentOffset = 0;

                while (_currentOffset != serializedHeaders.Length)
                {
                    var entry = ParseHeader(serializedHeaders);
                    var header = new KeyValuePair<string, string>(entry.Item1, entry.Item2);

                    if (entry.Item3 == IndexationType.Indexed)
                    {
                        if (workingSet.Contains(header))
                            workingSet.RemoveAll(h => h.Equals(header));
                        else
                            workingSet.Add(header);
                    }
                    else
                    {
                        workingSet.Add(header);
                    }
                }

                _localRefSet = new SizedHeadersList(workingSet);

                for (int i = _localRefSet.Count - 1; i >= 0; --i)
                {
                    var header = _localRefSet[i];
                    if (!_localHeaderTable.Contains(header))
                        _localRefSet.RemoveAll(h => h.Equals(header));
                }

                return workingSet;
            }
            catch (Exception e)
            {
                throw new CompressionError(e);
            }
        }
        private void CompressHeader(Tuple<string, string, IAdditionalHeaderInfo> header,
                                        SizedHeadersList useHeadersTable)
        {
            byte prefix = 0;
            var headerName = header.Item1;
            var headerValue = header.Item2;
            var headerType = (header.Item3 as Indexation).Type;

            switch (headerType)
            {
                case IndexationType.WithoutIndexation:
                case IndexationType.Incremental:
                    prefix = 5;
                    break;
                case IndexationType.Substitution:
                    prefix = 6;
                    break;
                case IndexationType.Indexed:
                    CompressIndexed(new KeyValuePair<string, string>(headerName, headerValue), useHeadersTable);
                    return;
            }

            CompressNonIndexed(headerName, headerValue, headerType, prefix, useHeadersTable);
        }
        private Tuple<string, string, IAdditionalHeaderInfo> ParseHeader(byte[] bytes, SizedHeadersList useHeadersTable)
        {
            var type = GetHeaderType(bytes);
            int index = GetIndex(bytes, type);
            string name;
            string value;
            byte valueLen;
            byte nameLen;

            switch (type)
            {
                case IndexationType.Indexed:
                    var kv = useHeadersTable[index];
                    return new Tuple<string, string, IAdditionalHeaderInfo>(kv.Key, kv.Value, new Indexation(type));
                case IndexationType.Incremental:
                case IndexationType.WithoutIndexation:
                case IndexationType.Substitution:
                    //get replaced entry index. It's equal with the found index in our case
                    if (type == IndexationType.Substitution)
                    {
                        index = GetIndex(bytes, type);
                    }
                    if (index == 0)
                    {
                        nameLen = bytes[_currentOffset++];
                        name = Encoding.UTF8.GetString(bytes, _currentOffset, nameLen);
                        _currentOffset += nameLen;
                    }
                    else
                    {
                        //Index increased by 1 was sent
                        name = useHeadersTable[index - 1].Key;
                    }
                    valueLen = bytes[_currentOffset++];
                    value = Encoding.UTF8.GetString(bytes, _currentOffset, valueLen);
                    _currentOffset += valueLen;

                    ModifyTable(name, value, type, useHeadersTable, index - 1);

                    return new Tuple<string, string, IAdditionalHeaderInfo>(name, value, new Indexation(type));
            }

            return default(Tuple<string, string, IAdditionalHeaderInfo>);
        }
        //Method retypes as many headers as it can to be Indexed
        //and checks if headers marked as indexed are present in the headers table
        private void OptimizeInputAndSendOptimized(List<Tuple<string, string, IAdditionalHeaderInfo>> headers, SizedHeadersList useHeadersTable)
        {
            for (int i = 0; i < headers.Count; i++ )
            {
                var headerKv = new KeyValuePair<string, string>(headers[i].Item1, headers[i].Item2);
                IndexationType headerType = (headers[i].Item3 as Indexation).Type;

                int index = useHeadersTable.IndexOf(headerKv);

                //case headerType == IndexationType.Incremental
                //must not be considered because headers table can contain duplicates
                if (index != -1 && headerType == IndexationType.Substitution)
                {
                    CompressIndexed(headerKv, useHeadersTable);
                    headers.Remove(headers[i--]);
                }

                //If header marked as indexed, but not found in the table, compress it as incremental.
                if (index == -1 && headerType == IndexationType.Indexed)
                {
                    CompressNonIndexed(headerKv.Key, headerKv.Value, IndexationType.Incremental, 5, useHeadersTable);
                    headers.Remove(headers[i--]);
                }
            }
        }
        private void CompressNonIndexed(string headerName, string headerValue, IndexationType headerType, byte prefix,
                                        SizedHeadersList useHeadersTable)
        {
            int index = useHeadersTable.FindIndex(kv => kv.Key == headerName);

            byte nameLenBinary = 0; // headers cant be more then 255 characters length
            byte[] nameBinary = new byte[0];

            //It's necessary to form result array because partial writeToOutput stream can cause problems because of multithreading
            using (var stream = new MemoryStream(64))
            {
                byte[] indexBinary;
                byte valueLenBinary;
                byte[] valueBinary;

                if (index != -1)
                {
                    indexBinary = (index + 1).ToUVarInt(prefix);
                }
                else
                {
                    indexBinary = 0.ToUVarInt(prefix);
                    nameBinary = Encoding.UTF8.GetBytes(headerName);
                    nameLenBinary = (byte)nameBinary.Length;
                }

                //Set without index type
                indexBinary[0] |= (byte)headerType;

                valueBinary = Encoding.UTF8.GetBytes(headerValue);
                valueLenBinary = (byte)valueBinary.Length;

                stream.Write(indexBinary, 0, indexBinary.Length);

                //write replaced index. It's equal with the found index in our case
                if (headerType == IndexationType.Substitution)
                {
                    stream.Write(indexBinary, 0, indexBinary.Length);
                }

                if (index == -1)
                {
                    stream.WriteByte(nameLenBinary);
                    stream.Write(nameBinary, 0, nameBinary.Length);
                }

                stream.WriteByte(valueLenBinary);
                stream.Write(valueBinary, 0, valueBinary.Length);

                WriteToOutput(stream.GetBuffer(), 0, (int)stream.Position);
            }

            ModifyTable(headerName, headerValue, headerType, useHeadersTable, index);
        }
        private void CompressIndexed(KeyValuePair<string, string> header, SizedHeadersList useHeadersTable)
        {
            int index = useHeadersTable.FindIndex(kv => kv.Key == header.Key && kv.Value == header.Value);
            const byte prefix = 7;
            var bytes = index.ToUVarInt(prefix);

            //Set indexed type
            bytes[0] |= (byte) IndexationType.Indexed;

            WriteToOutput(bytes, 0, bytes.Length);
        }