/******************************************************************************
 *
 * Purpose:  Implementation of the CTiledChannel class.
 *
 * This class is used to implement band interleaved channels within a 
 * PCIDSK file (which are always packed, and FILE interleaved data from
 * external raw files which may not be packed. 
 * 
 ******************************************************************************
 * Copyright (c) 2009
 * PCI Geomatics, 50 West Wilmot Street, Richmond Hill, Ont, Canada
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "pcidsk_config.h"
#include "pcidsk_types.h"
#include "pcidsk_exception.h"
#include "channel/ctiledchannel.h"
#include "segment/sysblockmap.h"
#include "core/sysvirtualfile.h"
#include "core/cpcidskfile.h"
#include "core/pcidsk_utils.h"
#include <cassert>
#include <cstdlib>
#include <cstring>

#include "cpl_port.h"

using namespace PCIDSK;

/************************************************************************/
/*                           CTiledChannel()                            */
/************************************************************************/

CTiledChannel::CTiledChannel( PCIDSKBuffer &image_headerIn,
                              uint64 ih_offsetIn,
                              CPL_UNUSED PCIDSKBuffer &file_headerIn,
                              int channelnumIn,
                              CPCIDSKFile *fileIn,
                              eChanType pixel_typeIn )
        : CPCIDSKChannel( image_headerIn, ih_offsetIn, fileIn, pixel_typeIn, channelnumIn)
{
/* -------------------------------------------------------------------- */
/*      Establish the virtual file we will be accessing.                */
/* -------------------------------------------------------------------- */
    std::string filename;

    image_headerIn.Get(64,64,filename);

    assert( strstr(filename.c_str(),"SIS=") != nullptr );

    image = atoi(strstr(filename.c_str(),"SIS=") + 4);

    vfile = nullptr;

/* -------------------------------------------------------------------- */
/*      If this is an unassociated channel (i.e. an overview), we        */
/*      will set the size and blocksize values to something             */
/*      unreasonable and set them properly in EstablishAccess()         */
/* -------------------------------------------------------------------- */
    if( channelnumIn == -1 )
    {
        width = -1;
        height = -1;
        block_width = -1;
        block_height = -1;
    }
    tile_count = 0;
    tiles_per_row = 0;
    tiles_per_col = 0;
}

/************************************************************************/
/*                           ~CTiledChannel()                           */
/************************************************************************/

CTiledChannel::~CTiledChannel()

{
    try
    {
        Synchronize();
    }
    catch( const PCIDSKException& e )
    {
        fprintf(stderr, "Exception in ~CTiledChannel(): %s", e.what()); // ok
    }
}

/************************************************************************/
/*                          EstablishAccess()                           */
/************************************************************************/

void CTiledChannel::EstablishAccess() const

{
    if( vfile != nullptr )
        return;
    
/* -------------------------------------------------------------------- */
/*      Establish the virtual file to access this image.                */
/* -------------------------------------------------------------------- */
    SysBlockMap *bmap = dynamic_cast<SysBlockMap*>(
        file->GetSegment( SEG_SYS, "SysBMDir" ));

    if( bmap == nullptr )
        return ThrowPCIDSKException( "Unable to find SysBMDir segment." );

    vfile = bmap->GetVirtualFile( image );

/* -------------------------------------------------------------------- */
/*      Parse the header.                                               */
/* -------------------------------------------------------------------- */
    PCIDSKBuffer theader(128);
    std::string data_type;

    vfile->ReadFromFile( theader.buffer, 0, 128 );

    width = theader.GetInt(0,8);
    height = theader.GetInt(8,8);
    block_width = theader.GetInt(16,8);
    block_height = theader.GetInt(24,8);

    theader.Get(32,4,data_type);
    theader.Get(54, 8, compression);
    
    pixel_type = GetDataTypeFromName(data_type);
    if (pixel_type == CHN_UNKNOWN)
    {
        return ThrowPCIDSKException( "Unknown channel type: %s", 
                              data_type.c_str() );
    }
    if( block_width <= 0 || block_height <= 0 )
    {
        return ThrowPCIDSKException( "Invalid blocksize: %d x %d", 
                              block_width, block_height );
    }

/* -------------------------------------------------------------------- */
/*      Compute information on the tiles.                               */
/* -------------------------------------------------------------------- */
    tiles_per_row = (width + block_width - 1) / block_width;
    tiles_per_col = (height + block_height - 1) / block_height;
    tile_count = tiles_per_row * tiles_per_col;

/* -------------------------------------------------------------------- */
/*      Resize our tile info cache.                                     */
/* -------------------------------------------------------------------- */
    int tile_block_info_count = 
        (tile_count + tile_block_size - 1) / tile_block_size;

    tile_offsets.resize( tile_block_info_count );
    tile_sizes.resize( tile_block_info_count );
    tile_info_dirty.resize( tile_block_info_count, false );

/* -------------------------------------------------------------------- */
/*      Establish byte swapping.  Tiled data files are always big       */
/*      endian, regardless of what the headers might imply.             */
/* -------------------------------------------------------------------- */
    unsigned short test_value = 1;
    
    if( ((uint8 *) &test_value)[0] == 1 )
        needs_swap = pixel_type != CHN_8U;
    else
        needs_swap = false;
}

/************************************************************************/
/*                         LoadTileInfoBlock()                          */
/************************************************************************/

void CTiledChannel::LoadTileInfoBlock( int block )

{
    assert( tile_offsets[block].empty() );

/* -------------------------------------------------------------------- */
/*      How many tiles in this block?                                   */
/* -------------------------------------------------------------------- */
    int tiles_in_block = tile_block_size;

    if( block * tile_block_size + tiles_in_block > tile_count )
        tiles_in_block = tile_count - block * tile_block_size;

/* -------------------------------------------------------------------- */
/*      Resize the vectors for this block.                              */
/* -------------------------------------------------------------------- */
    tile_offsets[block].resize( tiles_in_block );
    tile_sizes[block].resize( tiles_in_block );

/* -------------------------------------------------------------------- */
/*      Read the offset and size data from disk.                        */
/* -------------------------------------------------------------------- */
    PCIDSKBuffer offset_map( tiles_in_block * 12 + 1 );
    PCIDSKBuffer size_map( tiles_in_block * 8 + 1 );

    vfile->ReadFromFile( offset_map.buffer, 
                         128 + block * tile_block_size * 12, 
                         tiles_in_block * 12 );
    vfile->ReadFromFile( size_map.buffer, 
                         128 + tile_count * 12 + block * tile_block_size * 8,
                         tiles_in_block * 8 );
    
    for( int i = 0; i < tiles_in_block; i++ )
    {
        char chSaved;
        char *target = offset_map.buffer + i*12;

        chSaved = target[12];
        target[12] = '\0';
        tile_offsets[block][i] = atouint64(target);
        target[12] = chSaved;

        target = size_map.buffer + i*8;
        chSaved = target[8];
        target[8] = '\0';
        tile_sizes[block][i] = atoi(target);
        target[8] = chSaved;
    }
}

/************************************************************************/
/*                         SaveTileInfoBlock()                          */
/************************************************************************/

void CTiledChannel::SaveTileInfoBlock( int block )

{
    assert( !tile_offsets[block].empty() );
    int tiles_in_block = static_cast<int>(tile_offsets[block].size());

/* -------------------------------------------------------------------- */
/*      Write the offset and size data to disk.                         */
/* -------------------------------------------------------------------- */
    PCIDSKBuffer offset_map( tiles_in_block * 12 + 1 );
    PCIDSKBuffer size_map( tiles_in_block * 8 + 1 );

    for( int i = 0; i < tiles_in_block; i++ )
    {
        if( tile_offsets[block][i] == (uint64) -1 
            || tile_offsets[block][i] == 0 )
            offset_map.Put( -1, i*12, 12 );
        else
            offset_map.Put( tile_offsets[block][i], i*12, 12 );

        size_map.Put( tile_sizes[block][i], i*8, 8 );
    }

    vfile->WriteToFile( offset_map.buffer, 
                        128 + block * tile_block_size * 12, 
                        tiles_in_block * 12 );
    vfile->WriteToFile( size_map.buffer, 
                        128 + tile_count * 12 + block * tile_block_size * 8,
                        tiles_in_block * 8 );

    tile_info_dirty[block] = false;
}

/************************************************************************/
/*                            GetTileInfo()                             */
/*                                                                      */
/*      Fetch the tile offset and size for the indicated tile.          */
/************************************************************************/

void CTiledChannel::GetTileInfo( int tile_index, uint64 &offset, int &size )

{
    int block = tile_index / tile_block_size;
    int index_within_block = tile_index - block * tile_block_size;

    if( tile_offsets[block].empty() )
        LoadTileInfoBlock( block );

    offset = tile_offsets[block][index_within_block];
    size = tile_sizes[block][index_within_block];
}

/************************************************************************/
/*                            SetTileInfo()                             */
/************************************************************************/

void CTiledChannel::SetTileInfo( int tile_index, uint64 offset, int size )

{
    int block = tile_index / tile_block_size;
    int index_within_block = tile_index - block * tile_block_size;

    if( tile_offsets[block].empty() )
        LoadTileInfoBlock( block );

    if( offset != tile_offsets[block][index_within_block]
        || size != tile_sizes[block][index_within_block] )
    {
        tile_offsets[block][index_within_block] = offset;
        tile_sizes[block][index_within_block] = size;
        
        tile_info_dirty[block] = true;
    }
}

/************************************************************************/
/*                            Synchronize()                             */
/*                                                                      */
/*      Flush updated blockmap to disk if it is dirty.                  */
/************************************************************************/

void CTiledChannel::Synchronize()

{
    if( tile_info_dirty.empty() )
        return;

    int i;

    for( i = 0; i < (int) tile_info_dirty.size(); i++ )
    {
        if( tile_info_dirty[i] )
            SaveTileInfoBlock( i );
    }

    vfile->Synchronize();
}

/************************************************************************/
/*                             ReadBlock()                              */
/************************************************************************/

int CTiledChannel::ReadBlock( int block_index, void *buffer,
                              int xoff, int yoff, 
                              int xsize, int ysize )

{
    int pixel_size = DataTypeSize(GetType());

/* -------------------------------------------------------------------- */
/*      Default window if needed.                                       */
/* -------------------------------------------------------------------- */
    if( xoff == -1 && yoff == -1 && xsize == -1 && ysize == -1 )
    {
        xoff = 0;
        yoff = 0;
        xsize = GetBlockWidth();
        ysize = GetBlockHeight();
    }

/* -------------------------------------------------------------------- */
/*      Validate Window                                                 */
/* -------------------------------------------------------------------- */
    if( xoff < 0 || xoff + xsize > GetBlockWidth()
        || yoff < 0 || yoff + ysize > GetBlockHeight() )
    {
        return ThrowPCIDSKException( 0,
            "Invalid window in ReadBloc(): xoff=%d,yoff=%d,xsize=%d,ysize=%d",
            xoff, yoff, xsize, ysize );
    }

    if( block_index < 0 || block_index >= tile_count )
    {
        return ThrowPCIDSKException( 0, "Requested non-existent block (%d)", 
                              block_index );
    }

/* -------------------------------------------------------------------- */
/*      Does this tile exist?  If not return a zeroed buffer.           */
/* -------------------------------------------------------------------- */
    uint64 tile_offset;
    int    tile_size;

    GetTileInfo( block_index, tile_offset, tile_size );

    if( tile_size == 0 )
    {
        memset( buffer, 0, GetBlockWidth() * GetBlockHeight() * pixel_size );
        return 1;
    }

/* -------------------------------------------------------------------- */
/*      The simplest case it an uncompressed direct and complete       */
/*      tile read into the destination buffer.                          */
/* -------------------------------------------------------------------- */
    if( xoff == 0 && xsize == GetBlockWidth() 
        && yoff == 0 && ysize == GetBlockHeight() 
        && tile_size == xsize * ysize * pixel_size 
        && compression == "NONE" )
    {
        vfile->ReadFromFile( buffer, tile_offset, tile_size );

        // Do byte swapping if needed.
        if( needs_swap )
            SwapPixels( buffer, pixel_type, xsize * ysize );

        return 1;
    }

/* -------------------------------------------------------------------- */
/*      Load uncompressed data, one scanline at a time, into the        */
/*      target buffer.                                                  */
/* -------------------------------------------------------------------- */
    if( compression == "NONE" )
    {
        int iy;

        for( iy = 0; iy < ysize; iy++ )
        {
            vfile->ReadFromFile( ((uint8 *) buffer) 
                                 + iy * xsize * pixel_size,
                                 tile_offset 
                                 + ((iy+yoff)*block_width + xoff) * pixel_size,
                                 xsize * pixel_size );
        }
        
        // Do byte swapping if needed.
        if( needs_swap )
            SwapPixels( buffer, pixel_type, xsize * ysize );
        
        return 1;
    }

/* -------------------------------------------------------------------- */
/*      Load the whole compressed data into a working buffer.           */
/* -------------------------------------------------------------------- */
    PCIDSKBuffer oCompressedData( tile_size );
    PCIDSKBuffer oUncompressedData( pixel_size * block_width * block_height );

    vfile->ReadFromFile( oCompressedData.buffer, tile_offset, tile_size );
    
/* -------------------------------------------------------------------- */
/*      Handle decompression.                                           */
/* -------------------------------------------------------------------- */
    if( compression == "RLE" )
    {
        RLEDecompressBlock( oCompressedData, oUncompressedData );
    }
    else if( STARTS_WITH(compression.c_str(), "JPEG") )
    {
        JPEGDecompressBlock( oCompressedData, oUncompressedData );
    }
    else
    {
        return ThrowPCIDSKException( 0,
            "Unable to read tile of unsupported compression type: %s",
            compression.c_str() );
    }

/* -------------------------------------------------------------------- */
/*      Swap if necessary.  TODO: there is some reason to doubt that    */
/*      the old implementation properly byte swapped compressed         */
/*      data.  Perhaps this should be conditional?                      */
/* -------------------------------------------------------------------- */
    if( needs_swap )
        SwapPixels( oUncompressedData.buffer, pixel_type, 
                  GetBlockWidth() * GetBlockHeight() );

/* -------------------------------------------------------------------- */
/*      Copy out the desired subwindow.                                 */
/* -------------------------------------------------------------------- */
    int iy;
    
    for( iy = 0; iy < ysize; iy++ )
    {
        memcpy( ((uint8 *) buffer) + iy * xsize * pixel_size,
                oUncompressedData.buffer 
                + ((iy+yoff)*block_width + xoff) * pixel_size,
                xsize * pixel_size );
    }

    return 1;
}

/************************************************************************/
/*                            IsTileEmpty()                             */
/************************************************************************/
bool CTiledChannel::IsTileEmpty(void *buffer) const
{
    assert(sizeof(int32) == 4); // just to be on the safe side...

    unsigned int num_dword = 
        (block_width * block_height * DataTypeSize(pixel_type)) / 4;
    unsigned int rem = 
        (block_width * block_height * DataTypeSize(pixel_type)) % 4;

    int32* int_buf = reinterpret_cast<int32*>(buffer);

    if (num_dword > 0) {
        for (unsigned int n = 0; n < num_dword; n++) {
            if (int_buf[n]) return false;
        }
    }

    char* char_buf = reinterpret_cast<char*>(int_buf + num_dword);
    if (rem > 0) {
        for (unsigned int n = 0; n < rem; n++) {
            if (char_buf[n]) return false;
        }
    }

    return true;
}

/************************************************************************/
/*                             WriteBlock()                             */
/************************************************************************/

int CTiledChannel::WriteBlock( int block_index, void *buffer )

{
    if( !file->GetUpdatable() )
        return ThrowPCIDSKException(0, "File not open for update in WriteBlock()" );

    InvalidateOverviews();

    int pixel_size = DataTypeSize(GetType());
    int pixel_count = GetBlockWidth() * GetBlockHeight();

    if( block_index < 0 || block_index >= tile_count )
    {
        return ThrowPCIDSKException( 0, "Requested non-existent block (%d)", 
                              block_index );
    }

/* -------------------------------------------------------------------- */
/*      Fetch existing tile offset and size.                            */
/* -------------------------------------------------------------------- */
    uint64 tile_offset;
    int    tile_size;

    GetTileInfo( block_index, tile_offset, tile_size );

/* -------------------------------------------------------------------- */
/*      The simplest case it an uncompressed direct and complete       */
/*      tile read into the destination buffer.                          */
/* -------------------------------------------------------------------- */
    if( compression == "NONE" 
        && tile_size == pixel_count * pixel_size )
    {
        // Do byte swapping if needed.
        if( needs_swap )
            SwapPixels( buffer, pixel_type, pixel_count );

        vfile->WriteToFile( buffer, tile_offset, tile_size );

        if( needs_swap )
            SwapPixels( buffer, pixel_type, pixel_count );

        return 1;
    }

    if ((int64)tile_offset == -1)
    {
        // Check if the tile is empty. If it is, we can skip writing it,
        // unless the tile is already dirty.
        bool is_empty = IsTileEmpty(buffer);

        if (is_empty) return 1; // we don't need to do anything else
    }

/* -------------------------------------------------------------------- */
/*      Copy the uncompressed data into a PCIDSKBuffer, and byte        */
/*      swap if needed.                                                 */
/* -------------------------------------------------------------------- */
    PCIDSKBuffer oUncompressedData( pixel_size * block_width * block_height );

    memcpy( oUncompressedData.buffer, buffer, 
            oUncompressedData.buffer_size );

    if( needs_swap )
        SwapPixels( oUncompressedData.buffer, pixel_type, pixel_count );

/* -------------------------------------------------------------------- */
/*      Compress the imagery.                                           */
/* -------------------------------------------------------------------- */
    PCIDSKBuffer oCompressedData;

    if( compression == "NONE" )
    {
        oCompressedData = oUncompressedData;
    }
    else if( compression == "RLE" )
    {
        RLECompressBlock( oUncompressedData, oCompressedData );
    }
    else if( STARTS_WITH(compression.c_str(), "JPEG") )
    {
        JPEGCompressBlock( oUncompressedData, oCompressedData );
    }
    else
    {
        return ThrowPCIDSKException( 0,
            "Unable to write tile of unsupported compression type: %s",
            compression.c_str() );
    }

/* -------------------------------------------------------------------- */
/*      If this fits in the existing space, just write it directly.     */
/* -------------------------------------------------------------------- */
    if( oCompressedData.buffer_size <= tile_size )
    {
        vfile->WriteToFile( oCompressedData.buffer, tile_offset, tile_size );

        tile_size = oCompressedData.buffer_size;
        SetTileInfo( block_index, tile_offset, tile_size );
    }

/* -------------------------------------------------------------------- */
/*      Otherwise we try and write it at the end of the virtual file.   */
/* -------------------------------------------------------------------- */
    else
    {
        uint64 new_offset = vfile->GetLength();
        
        vfile->WriteToFile( oCompressedData.buffer, 
                            new_offset, oCompressedData.buffer_size );

        SetTileInfo( block_index, new_offset, oCompressedData.buffer_size );
    }

    return 1;
}

/************************************************************************/
/*                           GetBlockWidth()                            */
/************************************************************************/

int CTiledChannel::GetBlockWidth() const

{
    EstablishAccess();
    return CPCIDSKChannel::GetBlockWidth();
}

/************************************************************************/
/*                           GetBlockHeight()                           */
/************************************************************************/

int CTiledChannel::GetBlockHeight() const

{
    EstablishAccess();
    return CPCIDSKChannel::GetBlockHeight();
}

/************************************************************************/
/*                              GetWidth()                              */
/************************************************************************/

int CTiledChannel::GetWidth() const

{
    if( width == -1 )
        EstablishAccess();

    return CPCIDSKChannel::GetWidth();
}

/************************************************************************/
/*                             GetHeight()                              */
/************************************************************************/

int CTiledChannel::GetHeight() const

{
    if( height == -1 )
        EstablishAccess();

    return CPCIDSKChannel::GetHeight();
}

/************************************************************************/
/*                              GetType()                               */
/************************************************************************/

eChanType CTiledChannel::GetType() const

{
    if( pixel_type == CHN_UNKNOWN )
        EstablishAccess();

    return CPCIDSKChannel::GetType();
}

/************************************************************************/
/*                         RLEDecompressBlock()                         */
/************************************************************************/

void CTiledChannel::RLEDecompressBlock( PCIDSKBuffer &oCompressedData,
                                        PCIDSKBuffer &oDecompressedData )


{
    int    src_offset=0, dst_offset=0;
    uint8  *src = (uint8 *) oCompressedData.buffer;
    uint8  *dst = (uint8 *) oDecompressedData.buffer;
    int    pixel_size = DataTypeSize(GetType());

/* -------------------------------------------------------------------- */
/*      Process till we are out of source data, or our destination      */
/*      buffer is full.  These conditions should be satisfied at        */
/*      the same time!                                                  */
/* -------------------------------------------------------------------- */
    while( src_offset + 1 + pixel_size <= oCompressedData.buffer_size
           && dst_offset < oDecompressedData.buffer_size )
    {
/* -------------------------------------------------------------------- */
/*      Extract a repeat run                                            */
/* -------------------------------------------------------------------- */
        if( src[src_offset] > 127 )
        {
            int count = src[src_offset++] - 128;
            int i;

            if( dst_offset + count * pixel_size > oDecompressedData.buffer_size)
            {
                return ThrowPCIDSKException( "RLE compressed tile corrupt, overrun avoided." );
            }

            while( count-- > 0 )
            {
                for( i = 0; i < pixel_size; i++ )
                    dst[dst_offset++] = src[src_offset+i];
            }
            src_offset += pixel_size;
        }

/* -------------------------------------------------------------------- */
/*      Extract a literal run.                                          */
/* -------------------------------------------------------------------- */
        else 
        {
            int count = src[src_offset++];

            if( dst_offset + count*pixel_size > oDecompressedData.buffer_size
                || src_offset + count*pixel_size > oCompressedData.buffer_size)
            {
                return ThrowPCIDSKException( "RLE compressed tile corrupt, overrun avoided." );
            }

            memcpy( dst + dst_offset, src + src_offset, 
                    pixel_size * count );
            src_offset += pixel_size * count;
            dst_offset += pixel_size * count;
        }

    }

/* -------------------------------------------------------------------- */
/*      Final validation.                                               */
/* -------------------------------------------------------------------- */
    if( src_offset != oCompressedData.buffer_size 
        || dst_offset != oDecompressedData.buffer_size ) 
    {
        return ThrowPCIDSKException( "RLE compressed tile corrupt, result incomplete." );
    }
}

/************************************************************************/
/*                         RLECompressBlock()                           */
/*                                                                      */
/*      TODO: There does not seem to be any byte order logic in here!   */
/************************************************************************/

void CTiledChannel::RLECompressBlock( PCIDSKBuffer &oUncompressedData,
                                      PCIDSKBuffer &oCompressedData )

{
    int    src_bytes = oUncompressedData.buffer_size;
    int    pixel_size = DataTypeSize(GetType());
    int    src_offset = 0, dst_offset = 0;
    int    i;
    uint8  *src = (uint8 *) oUncompressedData.buffer;

/* -------------------------------------------------------------------- */
/*      Loop till input exhausted.                                      */
/* -------------------------------------------------------------------- */
    while( src_offset < src_bytes )
    {
        bool    bGotARun = false;

/* -------------------------------------------------------------------- */
/*      Establish the run length, and emit if greater than 3.           */
/* -------------------------------------------------------------------- */
        if( src_offset + 3*pixel_size < src_bytes )
        {
            int         count = 1;

            while( count < 127
                   && src_offset + count*pixel_size < src_bytes )
            {
                bool    bWordMatch = true;

                for( i = 0; i < pixel_size; i++ )
                {
                    if( src[src_offset+i] 
                        != src[src_offset+i+count*pixel_size] )
                        bWordMatch = false;
                }

                if( !bWordMatch )
                    break;

                count++;
            }

            if( count >= 3 )
            {
                if( oCompressedData.buffer_size < dst_offset + pixel_size+1 )
                    oCompressedData.SetSize( oCompressedData.buffer_size*2+100);

                oCompressedData.buffer[dst_offset++] = (char) (count+128);

                for( i = 0; i < pixel_size; i++ )
                    oCompressedData.buffer[dst_offset++] = src[src_offset+i];

                src_offset += count * pixel_size;

                bGotARun = true;
            }
            else
                bGotARun = false;
        }
        
/* -------------------------------------------------------------------- */
/*      Otherwise emit a literal till we encounter at least a three     */
/*      word series.                                                    */
/* -------------------------------------------------------------------- */
        if( !bGotARun )
        {
            int         count = 1;
            int         match_count = 0;

            while( count < 127
                   && src_offset + count*pixel_size < src_bytes )
            {
                bool    bWordMatch = true;

                for( i = 0; i < pixel_size; i++ )
                {
                    if( src[src_offset+i]
                        != src[src_offset+i+count*pixel_size] )
                        bWordMatch = false;
                }

                if( bWordMatch )
                    match_count++;
                else
                    match_count = 0;

                if( match_count > 2 )
                    break;

                count++;
            }
            
            assert( src_offset + count*pixel_size <= src_bytes );

            while( oCompressedData.buffer_size 
                   < dst_offset + count*pixel_size+1 )
                oCompressedData.SetSize( oCompressedData.buffer_size*2+100 );

            oCompressedData.buffer[dst_offset++] = (char) count;
            memcpy( oCompressedData.buffer + dst_offset, 
                    src + src_offset, 
                    count * pixel_size );
            src_offset += count * pixel_size;
            dst_offset += count * pixel_size;
        }
    }

    oCompressedData.buffer_size = dst_offset;
}

/************************************************************************/
/*                        JPEGDecompressBlock()                         */
/************************************************************************/

void CTiledChannel::JPEGDecompressBlock( PCIDSKBuffer &oCompressedData,
                                         PCIDSKBuffer &oDecompressedData )

                               
{
    if( file->GetInterfaces()->JPEGDecompressBlock == nullptr )
        return ThrowPCIDSKException( "JPEG decompression not enabled in the PCIDSKInterfaces of this build." );

    file->GetInterfaces()->JPEGDecompressBlock( 
        (uint8 *) oCompressedData.buffer, oCompressedData.buffer_size,
        (uint8 *) oDecompressedData.buffer, oDecompressedData.buffer_size,
        GetBlockWidth(), GetBlockHeight(), GetType() );
}

/************************************************************************/
/*                         JPEGCompressBlock()                          */
/************************************************************************/

void CTiledChannel::JPEGCompressBlock( PCIDSKBuffer &oDecompressedData,
                                       PCIDSKBuffer &oCompressedData )
{
    if( file->GetInterfaces()->JPEGCompressBlock == nullptr )
        return ThrowPCIDSKException( "JPEG compression not enabled in the PCIDSKInterfaces of this build." );

/* -------------------------------------------------------------------- */
/*      What quality should we be using?                                */
/* -------------------------------------------------------------------- */
#if 0
    int quality = 75;

    if( compression.c_str()[4] >= '1'
        && compression.c_str()[4] <= '0' )
        quality = atoi(compression.c_str() + 4);
#endif

/* -------------------------------------------------------------------- */
/*      Make the output buffer plenty big to hold any conceivable       */
/*      result.                                                         */
/* -------------------------------------------------------------------- */
    oCompressedData.SetSize( oDecompressedData.buffer_size * 2 + 1000 );

/* -------------------------------------------------------------------- */
/*      invoke.                                                         */
/* -------------------------------------------------------------------- */
    file->GetInterfaces()->JPEGCompressBlock(
        (uint8 *) oDecompressedData.buffer, oDecompressedData.buffer_size,
        (uint8 *) oCompressedData.buffer, oCompressedData.buffer_size,
        GetBlockWidth(), GetBlockHeight(), GetType(), 75 );
}
