/* BITMAPINFOHEADER struct and byte offsets:
         *  typedef struct tagBITMAPINFOHEADER{
         *    DWORD  biSize;           // 14
         *    LONG   biWidth;          // 18
         *    LONG   biHeight;         // 22
         *    WORD   biPlanes;         // 26
         *    WORD   biBitCount;       // 28
         *    DWORD  biCompression;    // 30
         *    DWORD  biSizeImage;      // 34
         *    LONG   biXPelsPerMeter;  // 38
         *    LONG   biYPelsPerMeter;  // 42
         *    DWORD  biClrUsed;        // 46
         *    DWORD  biClrImportant;   // 50
         *  } BITMAPINFOHEADER, *PBITMAPINFOHEADER;
         */

        private void ReadIndexedMemoryBitmap(int bits /*, ref bool hasAlpha*/)
        {
            int  pdfVersion = Owner.Version;
            int  firstMaskColor = -1, lastMaskColor = -1;
            bool segmentedColorMask = false;

            MemoryStream memory = new MemoryStream();
            // THHO4THHO Use ImageImporterBMP here to avoid redundant code.

            int streamLength = (int)memory.Length;

            Debug.Assert(streamLength > 0, "Bitmap image encoding failed.");
            if (streamLength > 0)
            {
                byte[] imageBits = new byte[streamLength];
                memory.Seek(0, SeekOrigin.Begin);
                memory.Read(imageBits, 0, streamLength);
                memory.Dispose();

                int height = _image.PixelHeight;
                int width  = _image.PixelWidth;

                if (ReadWord(imageBits, 0) != 0x4d42 || // "BM"
                    ReadDWord(imageBits, 2) != streamLength ||
                    ReadDWord(imageBits, 14) != 40 ||   // sizeof BITMAPINFOHEADER
                    ReadDWord(imageBits, 18) != width ||
                    ReadDWord(imageBits, 22) != height)
                {
                    throw new NotImplementedException("ReadIndexedMemoryBitmap: unsupported format");
                }
                int fileBits = ReadWord(imageBits, 28);
                if (fileBits != bits)
                {
                    if (fileBits == 1 || fileBits == 4 || fileBits == 8)
                    {
                        bits = fileBits;
                    }
                }

                if (ReadWord(imageBits, 26) != 1 ||
                    ReadWord(imageBits, 28) != bits ||
                    ReadDWord(imageBits, 30) != 0)
                {
                    throw new NotImplementedException("ReadIndexedMemoryBitmap: unsupported format #2");
                }

                int       bytesFileOffset         = ReadDWord(imageBits, 10);
                const int bytesColorPaletteOffset = 0x36; // GDI+ always returns Windows bitmaps: sizeof BITMAPFILEHEADER + sizeof BITMAPINFOHEADER
                int       paletteColors           = ReadDWord(imageBits, 46);
                if ((bytesFileOffset - bytesColorPaletteOffset) / 4 != paletteColors)
                {
                    throw new NotImplementedException("ReadIndexedMemoryBitmap: unsupported format #3");
                }

                MonochromeMask mask = new MonochromeMask(width, height);

                bool   isGray      = bits == 8 && (paletteColors == 256 || paletteColors == 0);
                int    isBitonal   = 0; // 0: false; >0: true; <0: true (inverted)
                byte[] paletteData = new byte[3 * paletteColors];
                for (int color = 0; color < paletteColors; ++color)
                {
                    paletteData[3 * color]     = imageBits[bytesColorPaletteOffset + 4 * color + 2];
                    paletteData[3 * color + 1] = imageBits[bytesColorPaletteOffset + 4 * color + 1];
                    paletteData[3 * color + 2] = imageBits[bytesColorPaletteOffset + 4 * color + 0];
                    if (isGray)
                    {
                        isGray = paletteData[3 * color] == paletteData[3 * color + 1] &&
                                 paletteData[3 * color] == paletteData[3 * color + 2];
                    }

                    if (imageBits[bytesColorPaletteOffset + 4 * color + 3] < 128)
                    {
                        // We treat this as transparency:
                        if (firstMaskColor == -1)
                        {
                            firstMaskColor = color;
                        }
                        if (lastMaskColor == -1 || lastMaskColor == color - 1)
                        {
                            lastMaskColor = color;
                        }
                        if (lastMaskColor != color)
                        {
                            segmentedColorMask = true;
                        }
                    }
                    //else
                    //{
                    //  // We treat this as opacity:
                    //}
                }

                if (bits == 1)
                {
                    if (paletteColors == 0)
                    {
                        isBitonal = 1;
                    }
                    if (paletteColors == 2)
                    {
                        if (paletteData[0] == 0 &&
                            paletteData[1] == 0 &&
                            paletteData[2] == 0 &&
                            paletteData[3] == 255 &&
                            paletteData[4] == 255 &&
                            paletteData[5] == 255)
                        {
                            isBitonal = 1; // Black on white
                        }
                        if (paletteData[5] == 0 &&
                            paletteData[4] == 0 &&
                            paletteData[3] == 0 &&
                            paletteData[2] == 255 &&
                            paletteData[1] == 255 &&
                            paletteData[0] == 255)
                        {
                            isBitonal = -1; // White on black
                        }
                    }
                }

                // NYI: (no sample found where this was required)
                // if (segmentedColorMask = true)
                // { ... }

                bool   isFaxEncoding = false;
                byte[] imageData     = new byte[((width * bits + 7) / 8) * height];
                byte[] imageDataFax  = null;
                int    k             = 0;

                if (bits == 1)
                {
                    // TODO: flag/option?
                    // We try Group 3 1D and Group 4 (2D) encoding here and keep the smaller byte array.
                    //byte[] temp = new byte[imageData.Length];
                    //int ccittSize = DoFaxEncoding(ref temp, imageBits, (uint)bytesFileOffset, (uint)width, (uint)height);

                    // It seems that Group 3 2D encoding never beats both other encodings, therefore we don't call it here.
                    //byte[] temp2D = new byte[imageData.Length];
                    //uint dpiY = (uint)image.VerticalResolution;
                    //uint kTmp = 0;
                    //int ccittSize2D = DoFaxEncoding2D((uint)bytesFileOffset, ref temp2D, imageBits, (uint)width, (uint)height, dpiY, out kTmp);
                    //k = (int) kTmp;

                    byte[] tempG4      = new byte[imageData.Length];
                    int    ccittSizeG4 = DoFaxEncodingGroup4(ref tempG4, imageBits, (uint)bytesFileOffset, (uint)width, (uint)height);

                    isFaxEncoding = /*ccittSize > 0 ||*/ ccittSizeG4 > 0;
                    if (isFaxEncoding)
                    {
                        //if (ccittSize == 0)
                        //  ccittSize = 0x7fffffff;
                        if (ccittSizeG4 == 0)
                        {
                            ccittSizeG4 = 0x7fffffff;
                        }
                        //if (ccittSize <= ccittSizeG4)
                        //{
                        //  Array.Resize(ref temp, ccittSize);
                        //  imageDataFax = temp;
                        //  k = 0;
                        //}
                        //else
                        {
                            Array.Resize(ref tempG4, ccittSizeG4);
                            imageDataFax = tempG4;
                            k            = -1;
                        }
                    }
                }

                //if (!isFaxEncoding)
                {
                    int bytesOffsetRead = 0;
                    if (bits == 8 || bits == 4 || bits == 1)
                    {
                        int bytesPerLine = (width * bits + 7) / 8;
                        for (int y = 0; y < height; ++y)
                        {
                            mask.StartLine(y);
                            int bytesOffsetWrite = (height - 1 - y) * ((width * bits + 7) / 8);
                            for (int x = 0; x < bytesPerLine; ++x)
                            {
                                if (isGray)
                                {
                                    // Lookup the gray value from the palette:
                                    imageData[bytesOffsetWrite] = paletteData[3 * imageBits[bytesFileOffset + bytesOffsetRead]];
                                }
                                else
                                {
                                    // Store the palette index.
                                    imageData[bytesOffsetWrite] = imageBits[bytesFileOffset + bytesOffsetRead];
                                }
                                if (firstMaskColor != -1)
                                {
                                    int n = imageBits[bytesFileOffset + bytesOffsetRead];
                                    if (bits == 8)
                                    {
                                        // TODO???: segmentedColorMask == true => bad mask NYI
                                        mask.AddPel((n >= firstMaskColor) && (n <= lastMaskColor));
                                    }
                                    else if (bits == 4)
                                    {
                                        // TODO???: segmentedColorMask == true => bad mask NYI
                                        int n1 = (n & 0xf0) / 16;
                                        int n2 = (n & 0x0f);
                                        mask.AddPel((n1 >= firstMaskColor) && (n1 <= lastMaskColor));
                                        mask.AddPel((n2 >= firstMaskColor) && (n2 <= lastMaskColor));
                                    }
                                    else if (bits == 1)
                                    {
                                        // TODO???: segmentedColorMask == true => bad mask NYI
                                        for (int bit = 1; bit <= 8; ++bit)
                                        {
                                            int n1 = (n & 0x80) / 128;
                                            mask.AddPel((n1 >= firstMaskColor) && (n1 <= lastMaskColor));
                                            n *= 2;
                                        }
                                    }
                                }
                                bytesOffsetRead  += 1;
                                bytesOffsetWrite += 1;
                            }
                            bytesOffsetRead = 4 * ((bytesOffsetRead + 3) / 4); // Align to 32 bit boundary
                        }
                    }
                    else
                    {
                        throw new NotImplementedException("ReadIndexedMemoryBitmap: unsupported format #3");
                    }
                }

                FlateDecode fd = new FlateDecode();
                if (firstMaskColor != -1 &&
                    lastMaskColor != -1)
                {
                    // Color mask requires Reader 4.0 or higher:
                    //if (!segmentedColorMask && pdfVersion >= 13)
                    if (!segmentedColorMask && pdfVersion >= 13 && !isGray)
                    {
                        PdfArray array = new PdfArray(_document);
                        array.Elements.Add(new PdfInteger(firstMaskColor));
                        array.Elements.Add(new PdfInteger(lastMaskColor));
                        Elements[Keys.Mask] = array;
                    }
                    else
                    {
                        // Monochrome mask
                        byte[]        maskDataCompressed = fd.Encode(mask.MaskData, _document.Options.FlateEncodeMode);
                        PdfDictionary pdfMask            = new PdfDictionary(_document);
                        pdfMask.Elements.SetName(Keys.Type, "/XObject");
                        pdfMask.Elements.SetName(Keys.Subtype, "/Image");

                        Owner._irefTable.Add(pdfMask);
                        pdfMask.Stream = new PdfStream(maskDataCompressed, pdfMask);
                        pdfMask.Elements[PdfStream.Keys.Length] = new PdfInteger(maskDataCompressed.Length);
                        pdfMask.Elements[PdfStream.Keys.Filter] = new PdfName("/FlateDecode");
                        pdfMask.Elements[Keys.Width]            = new PdfInteger(width);
                        pdfMask.Elements[Keys.Height]           = new PdfInteger(height);
                        pdfMask.Elements[Keys.BitsPerComponent] = new PdfInteger(1);
                        pdfMask.Elements[Keys.ImageMask]        = new PdfBoolean(true);
                        Elements[Keys.Mask] = pdfMask.Reference;
                    }
                }

                byte[] imageDataCompressed    = fd.Encode(imageData, _document.Options.FlateEncodeMode);
                byte[] imageDataFaxCompressed = isFaxEncoding ? fd.Encode(imageDataFax, _document.Options.FlateEncodeMode) : null;

                bool usesCcittEncoding = false;
                if (isFaxEncoding &&
                    (imageDataFax.Length < imageDataCompressed.Length ||
                     imageDataFaxCompressed.Length < imageDataCompressed.Length))
                {
                    // /CCITTFaxDecode creates the smaller file (with or without /FlateDecode):
                    usesCcittEncoding = true;

                    if (imageDataFax.Length < imageDataCompressed.Length)
                    {
                        Stream = new PdfStream(imageDataFax, this);
                        Elements[PdfStream.Keys.Length] = new PdfInteger(imageDataFax.Length);
                        Elements[PdfStream.Keys.Filter] = new PdfName("/CCITTFaxDecode");
                        //PdfArray array2 = new PdfArray(_document);
                        PdfDictionary dictionary = new PdfDictionary();
                        if (k != 0)
                        {
                            dictionary.Elements.Add("/K", new PdfInteger(k));
                        }
                        if (isBitonal < 0)
                        {
                            dictionary.Elements.Add("/BlackIs1", new PdfBoolean(true));
                        }
                        dictionary.Elements.Add("/EndOfBlock", new PdfBoolean(false));
                        dictionary.Elements.Add("/Columns", new PdfInteger(width));
                        dictionary.Elements.Add("/Rows", new PdfInteger(height));
                        //array2.Elements.Add(dictionary);
                        Elements[PdfStream.Keys.DecodeParms] = dictionary; // array2;
                    }
                    else
                    {
                        Stream = new PdfStream(imageDataFaxCompressed, this);
                        Elements[PdfStream.Keys.Length] = new PdfInteger(imageDataFaxCompressed.Length);
                        PdfArray arrayFilters = new PdfArray(_document);
                        arrayFilters.Elements.Add(new PdfName("/FlateDecode"));
                        arrayFilters.Elements.Add(new PdfName("/CCITTFaxDecode"));
                        Elements[PdfStream.Keys.Filter] = arrayFilters;
                        PdfArray arrayDecodeParms = new PdfArray(_document);

                        PdfDictionary dictFlateDecodeParms = new PdfDictionary();
                        //dictFlateDecodeParms.Elements.Add("/Columns", new PdfInteger(1));

                        PdfDictionary dictCcittFaxDecodeParms = new PdfDictionary();
                        if (k != 0)
                        {
                            dictCcittFaxDecodeParms.Elements.Add("/K", new PdfInteger(k));
                        }
                        if (isBitonal < 0)
                        {
                            dictCcittFaxDecodeParms.Elements.Add("/BlackIs1", new PdfBoolean(true));
                        }
                        dictCcittFaxDecodeParms.Elements.Add("/EndOfBlock", new PdfBoolean(false));
                        dictCcittFaxDecodeParms.Elements.Add("/Columns", new PdfInteger(width));
                        dictCcittFaxDecodeParms.Elements.Add("/Rows", new PdfInteger(height));

                        arrayDecodeParms.Elements.Add(dictFlateDecodeParms); // How to add the "null object"?
                        arrayDecodeParms.Elements.Add(dictCcittFaxDecodeParms);
                        Elements[PdfStream.Keys.DecodeParms] = arrayDecodeParms;
                    }
                }
                else
                {
                    // /FlateDecode creates the smaller file (or no monochrome bitmap):
                    Stream = new PdfStream(imageDataCompressed, this);
                    Elements[PdfStream.Keys.Length] = new PdfInteger(imageDataCompressed.Length);
                    Elements[PdfStream.Keys.Filter] = new PdfName("/FlateDecode");
                }

                Elements[Keys.Width]            = new PdfInteger(width);
                Elements[Keys.Height]           = new PdfInteger(height);
                Elements[Keys.BitsPerComponent] = new PdfInteger(bits);
                // TODO: CMYK

                // CCITT encoding: we need color palette for isBitonal == 0
                // FlateDecode: we need color palette for isBitonal <= 0 unless we have grayscales
                if ((usesCcittEncoding && isBitonal == 0) ||
                    (!usesCcittEncoding && isBitonal <= 0 && !isGray))
                {
                    PdfDictionary colorPalette = null;
                    colorPalette = new PdfDictionary(_document);
                    byte[] packedPaletteData = paletteData.Length >= 48 ? fd.Encode(paletteData, _document.Options.FlateEncodeMode) : null; // don't compress small palettes
                    if (packedPaletteData != null && packedPaletteData.Length + 20 < paletteData.Length)                                    // +20: compensate for the overhead (estimated value)
                    {
                        // Create compressed color palette:
                        colorPalette.CreateStream(packedPaletteData);
                        colorPalette.Elements[PdfStream.Keys.Length] = new PdfInteger(packedPaletteData.Length);
                        colorPalette.Elements[PdfStream.Keys.Filter] = new PdfName("/FlateDecode");
                    }
                    else
                    {
                        // Create uncompressed color palette:
                        colorPalette.CreateStream(paletteData);
                        colorPalette.Elements[PdfStream.Keys.Length] = new PdfInteger(paletteData.Length);
                    }
                    Owner._irefTable.Add(colorPalette);

                    PdfArray arrayColorSpace = new PdfArray(_document);
                    arrayColorSpace.Elements.Add(new PdfName("/Indexed"));
                    arrayColorSpace.Elements.Add(new PdfName("/DeviceRGB"));
                    arrayColorSpace.Elements.Add(new PdfInteger(paletteColors - 1));
                    arrayColorSpace.Elements.Add(colorPalette.Reference);
                    Elements[Keys.ColorSpace] = arrayColorSpace;
                }
                else
                {
                    Elements[Keys.ColorSpace] = new PdfName("/DeviceGray");
                }
                if (_image.Interpolate)
                {
                    Elements[Keys.Interpolate] = PdfBoolean.True;
                }
            }
        }
        /// <summary>
        /// Reads images that are returned from GDI+ without color palette.
        /// </summary>
        /// <param name="components">4 (32bpp RGB), 3 (24bpp RGB, 32bpp ARGB)</param>
        /// <param name="bits">8</param>
        /// <param name="hasAlpha">true (ARGB), false (RGB)</param>
        private void ReadTrueColorMemoryBitmap(int components, int bits, bool hasAlpha)
        {
            int          pdfVersion = Owner.Version;
            MemoryStream memory     = new MemoryStream();

            memory = _image.AsBitmap();
            // THHO4THHO Use ImageImporterBMP here to avoid redundant code.

            int streamLength = (int)memory.Length;

            Debug.Assert(streamLength > 0, "Bitmap image encoding failed.");
            if (streamLength > 0)
            {
                byte[] imageBits = new byte[streamLength];
                memory.Seek(0, SeekOrigin.Begin);
                memory.Read(imageBits, 0, streamLength);
                memory.Dispose();

                int height = _image.PixelHeight;
                int width  = _image.PixelWidth;

                // TODO: we could define structures for
                //   BITMAPFILEHEADER
                //   { BITMAPINFO }
                //   BITMAPINFOHEADER
                // to avoid ReadWord and ReadDWord ... (but w/o pointers this doesn't help much)

                bool bigHeader = false;
                if (ReadWord(imageBits, 0) != 0x4d42 || // "BM"
                    ReadDWord(imageBits, 2) != streamLength ||
                    ReadDWord(imageBits, 18) != width ||
                    ReadDWord(imageBits, 22) != height)
                {
                    throw new NotImplementedException("ReadTrueColorMemoryBitmap: unsupported format");
                }
                int infoHeaderSize = ReadDWord(imageBits, 14); // sizeof BITMAPINFOHEADER
                if (infoHeaderSize != 40 && infoHeaderSize != 108)
                {
                    throw new NotImplementedException("ReadTrueColorMemoryBitmap: unsupported format #2");
                }
                bigHeader = infoHeaderSize == 108;
                if (ReadWord(imageBits, 26) != 1 ||
                    (!hasAlpha && ReadWord(imageBits, bigHeader?30:28) != components * bits ||
                     hasAlpha && ReadWord(imageBits, bigHeader?30:28) != (components + 1) * bits) ||
                    bigHeader ? ReadWord(imageBits, 32) != 0 : ReadDWord(imageBits, 30) != 0)
                {
                    throw new NotImplementedException("ReadTrueColorMemoryBitmap: unsupported format #3");
                }

                int nFileOffset       = ReadDWord(imageBits, 10);
                int logicalComponents = components;
                if (components == 4)
                {
                    logicalComponents = 3;
                }

                byte[] imageData = new byte[components * width * height];

                bool           hasMask      = false;
                bool           hasAlphaMask = false;
                byte[]         alphaMask    = hasAlpha ? new byte[width * height] : null;
                MonochromeMask mask         = hasAlpha ?
                                              new MonochromeMask(width, height) : null;

                int nOffsetRead = 0;
                if (logicalComponents == 3)
                {
                    for (int y = 0; y < height; ++y)
                    {
                        int nOffsetWrite      = 3 * (height - 1 - y) * width;
                        int nOffsetWriteAlpha = 0;
                        if (hasAlpha)
                        {
                            mask.StartLine(y);
                            nOffsetWriteAlpha = (height - 1 - y) * width;
                        }

                        for (int x = 0; x < width; ++x)
                        {
                            imageData[nOffsetWrite]     = imageBits[nFileOffset + nOffsetRead + 2];
                            imageData[nOffsetWrite + 1] = imageBits[nFileOffset + nOffsetRead + 1];
                            imageData[nOffsetWrite + 2] = imageBits[nFileOffset + nOffsetRead];
                            if (hasAlpha)
                            {
                                mask.AddPel(imageBits[nFileOffset + nOffsetRead + 3]);
                                alphaMask[nOffsetWriteAlpha] = imageBits[nFileOffset + nOffsetRead + 3];
                                if (!hasMask || !hasAlphaMask)
                                {
                                    if (imageBits[nFileOffset + nOffsetRead + 3] != 255)
                                    {
                                        hasMask = true;
                                        if (imageBits[nFileOffset + nOffsetRead + 3] != 0)
                                        {
                                            hasAlphaMask = true;
                                        }
                                    }
                                }
                                ++nOffsetWriteAlpha;
                            }
                            nOffsetRead  += hasAlpha ? 4 : components;
                            nOffsetWrite += 3;
                        }
                        nOffsetRead = 4 * ((nOffsetRead + 3) / 4); // Align to 32 bit boundary
                    }
                }
                else if (components == 1)
                {
                    // Grayscale
                    throw new NotImplementedException("Image format not supported (grayscales).");
                }

                FlateDecode fd = new FlateDecode();
                if (hasMask)
                {
                    // monochrome mask is either sufficient or
                    // provided for compatibility with older reader versions
                    byte[]        maskDataCompressed = fd.Encode(mask.MaskData, _document.Options.FlateEncodeMode);
                    PdfDictionary pdfMask            = new PdfDictionary(_document);
                    pdfMask.Elements.SetName(Keys.Type, "/XObject");
                    pdfMask.Elements.SetName(Keys.Subtype, "/Image");

                    Owner._irefTable.Add(pdfMask);
                    pdfMask.Stream = new PdfStream(maskDataCompressed, pdfMask);
                    pdfMask.Elements[PdfStream.Keys.Length] = new PdfInteger(maskDataCompressed.Length);
                    pdfMask.Elements[PdfStream.Keys.Filter] = new PdfName("/FlateDecode");
                    pdfMask.Elements[Keys.Width]            = new PdfInteger(width);
                    pdfMask.Elements[Keys.Height]           = new PdfInteger(height);
                    pdfMask.Elements[Keys.BitsPerComponent] = new PdfInteger(1);
                    pdfMask.Elements[Keys.ImageMask]        = new PdfBoolean(true);
                    Elements[Keys.Mask] = pdfMask.Reference;
                }
                if (hasMask && hasAlphaMask && pdfVersion >= 14)
                {
                    // The image provides an alpha mask (requires Arcrobat 5.0 or higher)
                    byte[]        alphaMaskCompressed = fd.Encode(alphaMask, _document.Options.FlateEncodeMode);
                    PdfDictionary smask = new PdfDictionary(_document);
                    smask.Elements.SetName(Keys.Type, "/XObject");
                    smask.Elements.SetName(Keys.Subtype, "/Image");

                    Owner._irefTable.Add(smask);
                    smask.Stream = new PdfStream(alphaMaskCompressed, smask);
                    smask.Elements[PdfStream.Keys.Length] = new PdfInteger(alphaMaskCompressed.Length);
                    smask.Elements[PdfStream.Keys.Filter] = new PdfName("/FlateDecode");
                    smask.Elements[Keys.Width]            = new PdfInteger(width);
                    smask.Elements[Keys.Height]           = new PdfInteger(height);
                    smask.Elements[Keys.BitsPerComponent] = new PdfInteger(8);
                    smask.Elements[Keys.ColorSpace]       = new PdfName("/DeviceGray");
                    Elements[Keys.SMask] = smask.Reference;
                }

                byte[] imageDataCompressed = fd.Encode(imageData, _document.Options.FlateEncodeMode);

                Stream = new PdfStream(imageDataCompressed, this);
                Elements[PdfStream.Keys.Length] = new PdfInteger(imageDataCompressed.Length);
                Elements[PdfStream.Keys.Filter] = new PdfName("/FlateDecode");
                Elements[Keys.Width]            = new PdfInteger(width);
                Elements[Keys.Height]           = new PdfInteger(height);
                Elements[Keys.BitsPerComponent] = new PdfInteger(8);
                // TODO: CMYK
                Elements[Keys.ColorSpace] = new PdfName("/DeviceRGB");
                if (_image.Interpolate)
                {
                    Elements[Keys.Interpolate] = PdfBoolean.True;
                }
            }
        }