public void IsSymmetric()
        {
            var stream = new MemoryStream();

            var writer = new ReceiptWriter(stream);

            #region var receipts = new Receipt[3]
            var receipts = new Receipt[]
            {
                new Receipt
                {
                    Date = new DateTime(2012, 5, 30),
                    LoyaltyCard = 1013,
                    Store = 555,

                    Items = new Item[]
                    {
                        new Item
                        {
                            GTIN = "1234567890123",
                            UnitPrice = 150,
                            Discount = 0,
                            Quantity = 1,
                            VatRate = 196
                        },
                        new Item
                        {
                            GTIN = "1234567890123",
                            UnitPrice = 150,
                            Discount = 0,
                            Quantity = 1,
                            VatRate = 196
                        },
                        new Item
                        {
                            GTIN = "1234567890124",
                            UnitPrice = 50,
                            Discount = 10,
                            Quantity = 5,
                            VatRate = 196
                        },
                    }
                },

                new Receipt
                {
                    Date = new DateTime(2012, 5, 31),
                    LoyaltyCard = 1014,
                    Store = 555,

                    Items = new Item[]
                    {
                        new Item
                        {
                            GTIN = "1234567890124",
                            UnitPrice = 150,
                            Discount = 0,
                            Quantity = 1,
                            VatRate = 196
                        },
                        new Item
                        {
                            GTIN = "1234567890125",
                            UnitPrice = 250,
                            Discount = 0,
                            Quantity = 2,
                            VatRate = 196
                        },
                        new Item
                        {
                            GTIN = "1234567890126",
                            UnitPrice = 250,
                            Discount = 25,
                            Quantity = 1,
                            VatRate = 55
                        },
                    }
                },

                new Receipt
                {
                    Date = new DateTime(2012, 5, 31),
                    LoyaltyCard = 1015,
                    Store = 555,

                    Items = new Item[]
                    {
                        new Item
                        {
                            GTIN = "1234567890126",
                            UnitPrice = 50,
                            Discount = 0,
                            Quantity = 1,
                            VatRate = 196
                        },
                        new Item
                        {
                            GTIN = "1234567890125",
                            UnitPrice = 50,
                            Discount = 0,
                            Quantity = 1,
                            VatRate = 196
                        }
                    }
                },
            };
            #endregion

            writer.Write(receipts[0]);
            writer.Write(receipts[1]);
            writer.Write(receipts[2]);

            stream.Position = 0;

            var reader = new ReceiptReader(stream);

            var r0 = reader.Read();
            var r1 = reader.Read();
            var r2 = reader.Read();

            // Receipt top-level info
            Assert.AreEqual(receipts[0].Date, r0.Date);
            Assert.AreEqual(receipts[0].LoyaltyCard, r0.LoyaltyCard);
            Assert.AreEqual(receipts[0].Store, r0.Store);

            Assert.AreEqual(receipts[1].Date, r1.Date);
            Assert.AreEqual(receipts[1].LoyaltyCard, r1.LoyaltyCard);
            Assert.AreEqual(receipts[1].Store, r1.Store);

            Assert.AreEqual(receipts[2].Date, r2.Date);
            Assert.AreEqual(receipts[2].LoyaltyCard, r2.LoyaltyCard);
            Assert.AreEqual(receipts[2].Store, r2.Store);

            // Number of items
            Assert.AreEqual(receipts[0].Items.Length, r0.Items.Length);
            Assert.AreEqual(receipts[1].Items.Length, r1.Items.Length);
            Assert.AreEqual(receipts[2].Items.Length, r2.Items.Length);

            // First receipt
            Assert.AreEqual(receipts[0].Items[0].GTIN, r0.Items[0].GTIN);
            Assert.AreEqual(receipts[0].Items[0].UnitPrice, r0.Items[0].UnitPrice);
            Assert.AreEqual(receipts[0].Items[0].Quantity, r0.Items[0].Quantity);
            Assert.AreEqual(receipts[0].Items[0].Discount, r0.Items[0].Discount);
            Assert.AreEqual(receipts[0].Items[0].VatRate, r0.Items[0].VatRate);

            Assert.AreEqual(receipts[0].Items[1].GTIN, r0.Items[1].GTIN);
            Assert.AreEqual(receipts[0].Items[1].UnitPrice, r0.Items[1].UnitPrice);
            Assert.AreEqual(receipts[0].Items[1].Quantity, r0.Items[1].Quantity);
            Assert.AreEqual(receipts[0].Items[1].Discount, r0.Items[1].Discount);
            Assert.AreEqual(receipts[0].Items[1].VatRate, r0.Items[1].VatRate);

            Assert.AreEqual(receipts[0].Items[2].GTIN, r0.Items[2].GTIN);
            Assert.AreEqual(receipts[0].Items[2].UnitPrice, r0.Items[2].UnitPrice);
            Assert.AreEqual(receipts[0].Items[2].Quantity, r0.Items[2].Quantity);
            Assert.AreEqual(receipts[0].Items[2].Discount, r0.Items[2].Discount);
            Assert.AreEqual(receipts[0].Items[2].VatRate, r0.Items[2].VatRate);

            // Second receipt
            Assert.AreEqual(receipts[1].Items[0].GTIN, r1.Items[0].GTIN);
            Assert.AreEqual(receipts[1].Items[0].UnitPrice, r1.Items[0].UnitPrice);
            Assert.AreEqual(receipts[1].Items[0].Quantity, r1.Items[0].Quantity);
            Assert.AreEqual(receipts[1].Items[0].Discount, r1.Items[0].Discount);
            Assert.AreEqual(receipts[1].Items[0].VatRate, r1.Items[0].VatRate);

            Assert.AreEqual(receipts[1].Items[1].GTIN, r1.Items[1].GTIN);
            Assert.AreEqual(receipts[1].Items[1].UnitPrice, r1.Items[1].UnitPrice);
            Assert.AreEqual(receipts[1].Items[1].Quantity, r1.Items[1].Quantity);
            Assert.AreEqual(receipts[1].Items[1].Discount, r1.Items[1].Discount);
            Assert.AreEqual(receipts[1].Items[1].VatRate, r1.Items[1].VatRate);

            Assert.AreEqual(receipts[1].Items[2].GTIN, r1.Items[2].GTIN);
            Assert.AreEqual(receipts[1].Items[2].UnitPrice, r1.Items[2].UnitPrice);
            Assert.AreEqual(receipts[1].Items[2].Quantity, r1.Items[2].Quantity);
            Assert.AreEqual(receipts[1].Items[2].Discount, r1.Items[2].Discount);
            Assert.AreEqual(receipts[1].Items[2].VatRate, r1.Items[2].VatRate);

            // Third receipt
            Assert.AreEqual(receipts[2].Items[0].GTIN, r2.Items[0].GTIN);
            Assert.AreEqual(receipts[2].Items[0].UnitPrice, r2.Items[0].UnitPrice);
            Assert.AreEqual(receipts[2].Items[0].Quantity, r2.Items[0].Quantity);
            Assert.AreEqual(receipts[2].Items[0].Discount, r2.Items[0].Discount);
            Assert.AreEqual(receipts[2].Items[0].VatRate, r2.Items[0].VatRate);

            Assert.AreEqual(receipts[2].Items[1].GTIN, r2.Items[1].GTIN);
            Assert.AreEqual(receipts[2].Items[1].UnitPrice, r2.Items[1].UnitPrice);
            Assert.AreEqual(receipts[2].Items[1].Quantity, r2.Items[1].Quantity);
            Assert.AreEqual(receipts[2].Items[1].Discount, r2.Items[1].Discount);
            Assert.AreEqual(receipts[2].Items[1].VatRate, r2.Items[1].VatRate);
        }
        public Receipt Read()
        {
            var reader = new MyBinaryReader(_stream);

            // Reading the lookup updates

            int updateCount;
            try
            {
                updateCount = reader.Read7BitEncodedInt();
            }
            catch(EndOfStreamException)
            {
                return null;
            }

            for(int i = 0; i < updateCount; i++)
            {
                var flag = reader.Read7BitEncodedInt();

                var isNewValue = ((flag & 1) == 1);

                var valueType = flag >> 1; // discarding 1 bit

                switch (valueType)
                {
                    case 0: // GTINS
                        if(isNewValue)
                        {
                            _gtins.Add(reader.ReadString());
                        }
                        else
                        {
                            var oldIndex = reader.Read7BitEncodedInt();
                            var newIndex = reader.Read7BitEncodedInt();
                            _gtins.Swap(oldIndex, newIndex);
                        }

                        break;

                    case 1: // Prices
                        if(isNewValue)
                        {
                            _prices.Add(reader.ReadInt32());
                        }
                        else
                        {
                            var oldIndex = reader.Read7BitEncodedInt();
                            var newIndex = reader.Read7BitEncodedInt();
                            _prices.Swap(oldIndex, newIndex);
                        }
                        break;

                    case 2: // Discounts
                        if (isNewValue)
                        {
                            _discounts.Add(reader.ReadInt32());
                        }
                        else
                        {
                            var oldIndex = reader.Read7BitEncodedInt();
                            var newIndex = reader.Read7BitEncodedInt();
                            _discounts.Swap(oldIndex, newIndex);
                        }
                        break;

                    case 3: // VAT rates
                        if (isNewValue)
                        {
                            _vatRates.Add(reader.ReadInt32());
                        }
                        else
                        {
                            var oldIndex = reader.Read7BitEncodedInt();
                            var newIndex = reader.Read7BitEncodedInt();
                            _vatRates.Swap(oldIndex, newIndex);
                        }
                        break;

                    default:
                        throw new NotSupportedException();
                }
            }

            // Reading the receipt content
            var date = reader.ReadInt32().FromInt32ToDateTime();
            var loyaltyCard = reader.ReadInt32();
            var store = reader.ReadUInt16();

            var receipt = new Receipt
                {
                    Date = date,
                    LoyaltyCard = loyaltyCard,
                    Store = store
                };

            var itemCount = reader.Read7BitEncodedInt();
            var items = new Item[itemCount];

            for (int i = 0; i < itemCount; i++)
            {
                var gtinIndex = reader.Read7BitEncodedInt();
                var priceIndex = reader.Read7BitEncodedInt();

                var flag = reader.ReadByte();

                var nonOneQuantity = ((flag & 1) == 1);
                var nonZeroDiscount = ((flag & 2) == 2);

                var vatRateIndex = flag >> 2; // discarding two bits

                // fact: quantities equals to 1 amounts for 85% of the items.
                var quantity = nonOneQuantity ? reader.Read7BitEncodedInt() : 1;

                var discount = nonZeroDiscount ? reader.Read7BitEncodedInt() : 0;

                items[i] = new Item
                    {
                        GTIN = _gtins[gtinIndex],
                        UnitPrice = _prices[priceIndex],
                        Quantity = quantity,
                        Discount = discount,
                        VatRate = _vatRates[vatRateIndex]
                    };
            }

            receipt.Items = items;

            return receipt;
        }