// Copyleft 2012 Michael Pohoreski -- http://michael.PeopleOfHonorOnly.com/dev/lzw/lzw.cpp
// g++  -O3 -fomit-frame-pointer lzw.cpp -o lzw.bin

// Includes
    #include <stdio.h>  // fprintf
    #include <stdlib.h> // malloc, free, exit
    #include <string.h> // strlen, strcmp, memset
//  #include <unistd.h> // unlink -- if remove() not available

// Defines
    #define PRINT(...)         fprintf( stderr, __VA_ARGS__ )
    #define FATAL(...)         { PRINT(__VA_ARGS__); fflush( stderr ); exit( 1 ); }
    #define VERBOSE(...)       if ( g_nVerbose ) PRINT(__VA_ARGS__)
    #define MESSAGE(n,...)     if (g_nVerbose >= n) PRINT(__VA_ARGS__) 

// Types
    typedef unsigned char  u8;
    typedef unsigned short u16;
    typedef unsigned int   u32;

// Consts
    enum DictionarySizes {
          RESET_DICTIONARY = 256 // prefix: 0x00..0xFF data, 0x100 = reset dictionary entries, > 0x100 = new strings/codes
        , MAX_BITS    = 16
        , HASH_SIZE   = (1 << MAX_BITS) // Number of Dictionary.Entry[] -- In theory this should be the smallest prime > 16-bit, in practise a power of 2 is good enough. See: http://www.azillionmonkeys.com/qed/hash.c !
        , HASH_BUCKET = HASH_SIZE
        , CODE_SIZE   = (1 << MAX_BITS)  // Number of Dictionary.Code[] -- Decoding: "pointer" to dictionary entry: [code]=iEntry -> Dictionary[ iEntry ]
    };

    enum Flags {
          DST_FORCE_CONSOLE  = (1 << 0)
        , DST_FORCE_COMPRESS = (1 << 1)
        , DST_NOT_COMPRESSED = (1 << 2) // couldn't compress file -- output file needs to be deleted 
    };
    const char sExtension[] = ".LZW";

// Globals
    int   g_nVerbose = 0;      // 1 = Show encoding status, 0 = quiet
    char* g_pSrcName = NULL;   // pSrc memory buffer was allocated
    char* g_pDstName = NULL;   // compress: name with .LZW, decompress: name without .LZW
    FILE* g_pDstFile = stdout; // bOutputIsFile:  (g_pDstFile != stdout)  
    int   g_nFlags   = 0;

// Emit bytes _______________________________________________________________________________________________

    const u16 OUT_BUFFER_SIZE = (8 * 1024);
    /* */ u16 nOutBuffer = 0;
    /* */ u8  aOutBuffer[ OUT_BUFFER_SIZE ];
    inline void Out8( u8 byte )
    {
        aOutBuffer[ nOutBuffer++ ] = byte;
        if ((nOutBuffer & (OUT_BUFFER_SIZE-1)) == 0)
        {
            fwrite( aOutBuffer, (size_t)OUT_BUFFER_SIZE, (size_t)1, g_pDstFile ); // fputc( byte, g_pDstFile );
            nOutBuffer = 0;
        } 
    }

    int OutFlush( u8* pSrc )
    {
        if ( nOutBuffer )
            fwrite( aOutBuffer, (size_t)nOutBuffer, 1, g_pDstFile );
        if ( g_pDstFile != stdout ) // or (!(g_nFlags & DST_FORCE_CONSOLE)
        {
            fclose( g_pDstFile );
            if (g_nFlags & DST_NOT_COMPRESSED)
            {
                MESSAGE( 2, "Deleting '%s'\n", g_pDstName );
                remove( g_pDstName );
            }
            else
                if (!(g_nFlags & DST_FORCE_CONSOLE) && g_pSrcName)
                {
                    MESSAGE( 2, "Removing '%s'\n", g_pSrcName );
                    remove( g_pSrcName );
                }
        }
        if ( g_pDstName ) free( g_pDstName );
        if ( g_pSrcName ) free( pSrc ); // Note: g_pSrcName is not malloc'd, only the buffer is
        exit( 0 );
    }

// Compress Utility _________________________________________________________________________________________

    enum CompressFlags {
          COMPRESS_MAGIC_1     = 0x1F
        , COMPRESS_MAGIC_2     = 0x9D
        , COMPRESS_BLOCK       = 0x80
        , COMPRESS_BITMASK     = 0x1F
        , COMPRESS_HEADER_SIZE = 3    // 3 byte header
    };

    bool IsCompressHeader( u8 *pSrc )
    {
        if ((pSrc[0] != COMPRESS_MAGIC_1)
        ||  (pSrc[1] != COMPRESS_MAGIC_2)
        ||  (pSrc[2] != (COMPRESS_BLOCK | MAX_BITS)))
            return false;
        return true;
    }

    void OutCompressHeader() // OutputCompressedHeader
    {
        Out8( COMPRESS_MAGIC_1 ); // "Magic" Header Byte 1
        Out8( COMPRESS_MAGIC_2 ); // "Magic" Header Byte 2
        Out8( COMPRESS_BLOCK | (MAX_BITS & COMPRESS_BITMASK) ); // 0x10 = max 16-bit Dictionary
    }

// CRC32 ____________________________________________________________________________________________________ 

    u32 aCRC32[ 256 ];

    // ========================================================================
    void CRC32_Init()
    {
        const u32 CRC32_REVERSE = 0xEDB88320;
        for( u32 byte = 0; byte <= 0xFF; byte++ )
        {
            u32 crc = byte;
            for( char bit = 0; bit < 8; bit++ )
            {
                if (crc & 1)  crc = (crc >> 1) ^ CRC32_REVERSE;
                else          crc = (crc >> 1);
            }
            aCRC32[ byte ] = crc;
        }
    }

    // ========================================================================
    u32 CRC32_Begin()
    {
        return (u32)-1;
    }

    // ========================================================================
    u32 CRC32_Next( u32 crc, u8 byte )
    {
        crc = (crc >> 8) ^ aCRC32[ (crc ^ byte) & 0xFF ];
        return crc;
    }

    // ========================================================================
    u32 CRC32_End( u32 crc )
    {
        return ~crc;
    }

// Dictionary Entry _________________________________________________________________________________________

    // Codes are offsets into the Dictionary aEntry[] used in Decompress
    typedef u16 Code;

// ========================================================================
struct Entry // one Dictionary Entry // Total 9 bytes, Alloc size 12 bytes
{
    u32 _nHash  ; // used to prime hash of string <Prefix,Byte>  
    u16 _nCode  ; // In hashed searching we need to return its code
    u16 _nPrefix; // Prefix "String"  (either 'code/index/pointer' of preceeding string or preceeding byte 0x00..0xFF
    u8  _nByte  ; // Suffix "Character" which is appended to prefix string
   
    Entry( u16 prefix, u8 byte, u16 code )
    {
        _nPrefix = prefix;
        _nByte   = byte;
        _nCode   = code;
        // Note: An 3-byte CRC32 will be used in hashed searching -- ideally the Dictionary should generate a CRC32 of the _whole_  string! See: lzw_deep_crc.cpp
        _nHash   = Hash_CRC32(); // Alternatives: SuperFastHash: http://www.azillionmonkeys.com/qed/hash.c or FNV: http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
    }


    // In theory, CRC32 is a bad hash, in practice, as a 16-bit/24-bit hash it is OK
    // ========================================================================
    u32 Hash_CRC32()
    {
        u32    nHash = CRC32_Begin();
        /**/   nHash = CRC32_Next( nHash, (u8)((_nPrefix >> 8) & 0xFF) );
        /**/   nHash = CRC32_Next( nHash, (u8)((_nPrefix >> 0) & 0xFF) ); // assume MaxCode: 16-bit
        /**/   nHash = CRC32_Next( nHash,       _nByte                 );
        return nHash = CRC32_End( nHash );
    }
};

// Dictionary _______________________________________________________________________________________________

// Encoding: Given "string", return O(1) its code (aEntry[iEntry]) // FindString()
// Decoding: Given code    , return the string (aCodes[code] -> aEntry[iEntry] ... ) // FindCode()
// ========================================================================
class Dictionary
{
public:
    Dictionary( u32 size, bool isUncompress )
    {
        int encodeSize = (u32)(HASH_SIZE * sizeof( Entry ));
        int decodeSize = (u32)(CODE_SIZE * sizeof( Code  ));
        MESSAGE( 2, "Dictionary size: [%d] @ %2d bytes/entry = %d bytes (%d KB)\n", HASH_SIZE, (u32)sizeof(Entry), encodeSize, encodeSize / 1024 );
        MESSAGE( 2, "Decoding lookup: [%d] @ %2d bytes/entry = %d bytes (%d KB)\n", CODE_SIZE, (u32)sizeof(Code ), decodeSize, decodeSize / 1024 );
        MESSAGE( 2, "Max Dict entry: %d\n", ((1 << MAX_BITS) - 1) );

        aEntry = (Entry*) malloc( (size_t)(HASH_SIZE * sizeof( Entry )) );
        aCodes = (Code *) malloc( (size_t)(CODE_SIZE * sizeof( Code  )) );

        Reset();

        nOutputBits = 0;
        tOutputCode = 0;

        nByteOriginal = isUncompress ? 0 : size; // Uncompress: updated in OutUnpack()
        nByteCompress = isUncompress ? size : 0;
        nBytePercent  = 0.0f;

        nBitsOriginal = isUncompress ? 0 : size * 8;
        nBitsCompress = 0; // Updated in Encode() and Decode()
        nBitsPercent  = 0.0f;

        bDecompress   = isUncompress;
        nCRC32        = 0;
    }

    ~Dictionary()
    {
        if ( aEntry ) free( aEntry );
        if ( aCodes ) free( aCodes );
    }

    void Reset()
    {
        nCodeNext = RESET_DICTIONARY + 1;
        nCodeBits = 9;
        nCodeMask = (1 << nCodeBits) - 1;

        memset( aEntry, 0, (size_t)(HASH_SIZE * sizeof( Entry )) );
        memset( aCodes, 0, (size_t)(CODE_SIZE * sizeof( Code  )) );
    }

    void Add( u16 prefix, u8 byte )
    {
        if (nCodeNext >= (1 << MAX_BITS))
        {
            MESSAGE( 2, "Add() Warning: Dictionary full!  Couldn't add <%04X,%02X>\n", prefix, byte );
            return;
        }

        Entry add( prefix, byte, nCodeNext ); // Ideally, we would use an incremental/chain hash -- use the hash of Entry[prefix] and combine it with this hash for better distribution

        // Fast hashed insert (random-access)
        u32 iEntry = (add._nHash & (HASH_SIZE-1));
        int nHash  = HASH_BUCKET; // max entries to search = hash bucket size
        while (nHash-- > 0)
        {
            if (!aEntry[ iEntry ]._nCode) // BUGFIX: _nHash -> _nCode -- we never store codes 0x00..0xFF as an optimization, this allows us to check if an entry is free/used by checking its assigned code
            {
                aEntry[ iEntry ] = add;
                aCodes[ nCodeNext ] = iEntry;
                break;
            }
            // Have a collision!  Check if next slot is available
            iEntry++;
            iEntry &= (HASH_SIZE-1); // Note: To compare performance when HASH_SIZE is prime, use: iEntry %= HASH_SIZE;
        }
        nCodeNext++;
    }

    // Keep track of how many bits we need to represent codes in the range [0,nCodeNext)
    // e.g.
    //    if   nCodeNext = 256
    //    then nCodeBits = 9
    //         nCodeMask = (2^9)-1 = 0x1FF
    bool UpdateEncoder()
    {
        if (nCodeNext > nCodeMask) // BUGFIX: only do if nCodeNext <= 16 bits 
        {
            if (nCodeBits < MAX_BITS)
            {
                nCodeBits++;
                nCodeMask = (1 << nCodeBits) - 1;
            }
            else 
                return true;
        }
        return false;
    }

// Encoding _________________________________________________________________________________________________

    // Encoding
    // if Dictionary[ iEntry ] == { code,prefix } then return Dictionary[ iEntry ].code
    bool FindString( u16 prefix, u8 byte, u16 & iFoundCode_ )
    {
        Entry find( prefix, byte, 0 ); // only care about the hash value not the code

        // Fast hashed search (random-access)
        u32 iEntry = find._nHash & (HASH_SIZE-1); // current/starting entry
        int nHash  = HASH_BUCKET; // max entries to search = hash bucket size
        while (nHash-- > 0)
        {                                                   //
            if((aEntry[ iEntry ]._nHash == find._nHash)     //
            && (aEntry[ iEntry ]._nPrefix == prefix)        //
            && (aEntry[ iEntry ]._nByte == byte))           //
            {                                               //
                iFoundCode_ = aEntry[ iEntry ]._nCode;      //
                return true;                                //
            }                                               //
            if ( !aEntry[ iEntry ]._nHash )                 //
                break;                                      //
            iEntry++;                                       //
            iEntry &= (HASH_SIZE-1);                        //
        }                                                   //
        return false;
    }

    void PrintEncodingStatus()
    {
        nBitsOriginal = 8 * nByteOriginal;
        if ( !nByteOriginal ) nByteOriginal = 1;
        if ( !nBitsOriginal ) nBitsOriginal = 8;

        nBytePercent = 100.f * ((double)(nByteOriginal - nByteCompress)) / (double)nByteOriginal;
        nBitsPercent = 100.f * ((double)(nBitsOriginal - nBitsCompress)) / (double)nBitsOriginal;

        const char *sDecompress[2] = { "->", "<-" };
        VERBOSE( "CRC32: 0x%08X\n", nCRC32 );
        VERBOSE( "Bytes: %6.2f%%, %ld %s %ld (+%d bytes header)\n", nBytePercent, nByteOriginal, sDecompress[bDecompress], nByteCompress, COMPRESS_HEADER_SIZE );
        VERBOSE( "Bits : %6.2f%%, %ld %s %ld \n", nBitsPercent, nBitsOriginal, sDecompress[bDecompress], nBitsCompress );
    }

    // Encoding Output: write VBR code to 8-bit values
    void Encode( u16 code )
    {
        nBitsCompress += nCodeBits; // STATS only
        tOutputCode |= (((u32)(code & nCodeMask)) << nOutputBits);
        nOutputBits += nCodeBits;

        // output Variable Bits (1-16 bits) ...
        while (nOutputBits >= 8)
        {
            Out8( (u8)tOutputCode ); // Output raw binary bitstream
            tOutputCode >>= 8;
            nOutputBits -= 8;
            nByteCompress++; // STATS only
        }
    }

// Decoding _________________________________________________________________________________________________

    // Decoding Input VBR
    u16 Decode( u8* &pSrc )
    {
        nBitsCompress += nCodeBits; // STATS only
        u32 bytes = 
            (((u32)pSrc[0]) <<  0) |
            (((u32)pSrc[1]) <<  8) |
            (((u32)pSrc[2]) << 16) ;
        u16 code = (u16)((bytes >> nOutputBits) & nCodeMask);
        nOutputBits += nCodeBits;
        pSrc += (nOutputBits / 8); // +0,1, or 2
        nOutputBits &= 7; // while (n > 8) n -= 8;  -->  n %= 8  --> n & 7
        return code;
    }

    // returns TRUE if code exists in dictionary
    bool FindCode( u16 code, u16 & prefix_, u8 & byte_ )
    {
        if (code < RESET_DICTIONARY) // Trivial accept -- we don't actually store codes 0x00 .. 0xFF in the dictionary!
        {
            prefix_ = 0;
            byte_   = code;
            return true;
        }

        // We can have: aCodes[code] = 0 --> aEntry[0]
        int  iEntry = aCodes[ code ];
        if (aEntry[ iEntry ]._nCode == code)
        {
            prefix_ = aEntry[ iEntry ]._nPrefix;
            byte_   = aEntry[ iEntry ]._nByte;
            return true;
        }
        return false;
    }

    // Get the first byte in a string
    u8 First( u16 prefix )
    {
        u8 byte;
        if (prefix < RESET_DICTIONARY)
            byte = (u8)(prefix & 0xFF);
        else
        {
            u16 p;
            if( FindCode( prefix, p, byte ) )
                byte = First( p );
            else
                FATAL( "\n*** Error in bitstream: Couldn't get first byte of string! Code %04X wasn't added to dictionary!\n", prefix );
        }
        return byte;
    }

    // Decoding Output 

    // Unpack string -- ugh! Recursive ...
    u32 OutUnpack( u16 code, u32 crc )
    {
        if (code < RESET_DICTIONARY) // recursive base case
        {
            Out8( (u8)code );
            crc = CRC32_Next( crc, (u8)code );
            nByteOriginal++;
        }
        else
        {
            u16 prefix;
            u8  byte;
            if ( FindCode( code, prefix, byte ) )
            {
                crc = OutUnpack( prefix, crc );
                crc = OutUnpack( byte  , crc ); // tail recursion
            }
            else
                FATAL( "\n*** Error in bitstream: Couldn't get string! Code %04X not in the Dictionary!\n", prefix );
        }
        return crc;
    }

        Entry *aEntry; // data
        Code  *aCodes; // map Codes[code].iEntry -> aEntry[iEntry]

    // Output Bits
        u32    nCodeNext; // The next entry in the dictionary will be this number ...
        u32    nCodeBits; // ... and take this many bits.  Log2( nCodeNext )
        u32    nCodeMask; // 2^nCodeBits - 1
        u32    nOutputBits; // num of bits in the tOutputCode latch
        u32    tOutputCode; // simple barrel shifter latch of the codes to output

    // Stats
        size_t nByteOriginal;
        size_t nByteCompress;
        float  nBytePercent ; // Compression Ratio

        size_t nBitsOriginal;
        size_t nBitsCompress;
        float  nBitsPercent ; // Compression Ratio

        bool   bDecompress ; // STATS only
        u32    nCRC32;
};

// 1 = Output bitstream compressed 
// 0 = Output bitstream was unable to be compressed. Caller should delete.
// ========================================================================
void Compress( size_t nSrc, u8* pSrc ) // Map CBR 8-bit bytes to VBR 9-16 bit codes
{
    Dictionary dict( nSrc, false );

    if (pSrc && (nSrc > 0))
    {
        u8 * pEnd   = pSrc + nSrc;
        u16  prefix = *pSrc++;
        u8   byte;
        u16  code;
        u32  crc    = CRC32_Next( CRC32_Begin(), (u8)prefix );
        bool flush  = 0;

        OutCompressHeader();
        while (pSrc < pEnd)
        {
            byte = *pSrc++;
            crc = CRC32_Next( crc, byte );

            if( dict.FindString( prefix, byte, code ) )
                prefix = code;
            else
            {
                dict.Encode( prefix );
                flush = dict.UpdateEncoder(); 
                dict.Add( prefix, byte );
                prefix = byte;
            }

            if( flush )
            {
                flush = 0;
                dict.Encode( prefix ); // prefix );
                dict.Encode( RESET_DICTIONARY );
                dict.Reset();
                if (pSrc < pEnd)
                {
                    prefix = *pSrc++; // need to update 'prefix' to next byte ...
                    crc = CRC32_Next( crc, (u8)prefix );
                }
            }
        }

        if (((dict.nByteCompress + COMPRESS_HEADER_SIZE) > dict.nByteOriginal) && !(g_nFlags & DST_FORCE_COMPRESS))
        {
            VERBOSE( "Compressed header+data bytes larger then original (%d+%ld > %ld).  Skipping '%s'...\n", COMPRESS_HEADER_SIZE, dict.nByteCompress, dict.nByteOriginal, g_pDstName );
            g_nFlags |= DST_NOT_COMPRESSED; 
        }
        else
        {
            byte = 0;
            dict.Encode( prefix ); // output last prefix
            if( dict.nOutputBits )
            {
                dict.nCodeBits = 8 - dict.nOutputBits; // write last byte 
                dict.Encode( 0 ); // force flushing of last compressed byte
            }

            dict.nCRC32 = CRC32_End( crc );
            dict.PrintEncodingStatus();
        }
    }
}

// ========================================================================
void Decompress( size_t nSrc, u8 *pSrc ) // Map VBR 9-16 bit codes to CBR 8-bit bytes
{
    if( pSrc && (nSrc > 3) )
    {
        if( !IsCompressHeader( pSrc ))
        {
            FATAL( "Not compressed!\n" );
            return;
        }
        nSrc -= 3;
        pSrc += 3;

        Dictionary dict( nSrc, true );
        u8 * pEnd    = pSrc + nSrc - 1;
        u16  prefix  = dict.Decode( pSrc );
        u16  suffix  = 0;
        u16  p;
        u8   b;
        u8   byte = prefix;
        u32  crc = CRC32_Begin();

        crc = dict.OutUnpack( prefix, crc ); // always is byte --> Out8( (u8)prefix );
        while (pSrc < pEnd)
        {
            suffix = dict.Decode( pSrc );

            if (suffix == RESET_DICTIONARY)
            {
                dict.Reset();
                prefix = dict.Decode( pSrc );
                crc = dict.OutUnpack( prefix, crc ); // always is byte
            }
            else
            {
                if ( dict.FindCode( suffix, p, b ) )
                {
                    byte = dict.First( suffix );
                    crc = dict.OutUnpack( suffix, crc );
                }
                else
                {
                    // check if overflow decoder!
                    if (suffix >= dict.nCodeNext) 
                        byte = dict.First( prefix );
                    else
                        byte = dict.First( suffix );
                    crc = dict.OutUnpack( prefix, crc );
                    crc = dict.OutUnpack( byte, crc );
                }
                dict.Add( prefix, byte );
                dict.UpdateEncoder(); 
                prefix = suffix;
            }
        }
        dict.nCRC32 = CRC32_End( crc );
        dict.PrintEncodingStatus();
    }
}

// return TRUE buffer allocated for source file input, else FALSE
// ========================================================================
void File_ReadSrc( long nSrcName, const char *pSrcName, size_t& nSrcLen_, u8* &pSrcBuf_ )
{
    nSrcLen_ = (size_t)nSrcName;
    pSrcBuf_ = (u8*)   pSrcName;

    FILE * pSrcFile = fopen( pSrcName, "rb" );
    if ( pSrcFile )
    {
        fseek( pSrcFile, 0, SEEK_END ); 
        nSrcLen_ = ftell( pSrcFile );
        fseek( pSrcFile, 0, SEEK_SET );

        pSrcBuf_ = (u8*) malloc( nSrcLen_ + 4 ); // decompressor (may) need padding at EOF to get last code
        if( !pSrcBuf_ )
            FATAL( "Couldn't allocate source buffer of %ld bytes!\n", nSrcLen_ ); 

        memset( pSrcBuf_ + nSrcLen_, 0, 4 );
        fread( (void*) pSrcBuf_, (size_t) nSrcLen_, 1, pSrcFile );
        fclose( pSrcFile );

        g_pSrcName = (char*) pSrcName;
    }
}

// 1 = Append Extension
// 2 = Remove Extension
void File_MakeDst( const long nSrcNameLen, const char *pSrcName, bool bAppendExtension )
{
    size_t nExtension = strlen( sExtension ); 

    if (!(g_nFlags & DST_FORCE_CONSOLE) && (nSrcNameLen > nExtension)) // source name len > ".LZW"
    { 
        size_t nDstNameLen = nSrcNameLen + nExtension + 1; // ".LZW" + 0x00
        g_pDstName = (char*) calloc( (size_t)1, nDstNameLen );
        if ( !g_pDstName )
            FATAL( "Couldn't allocate destination name of %d bytes!\n", (int)nDstNameLen );

        strncpy( g_pDstName                  , pSrcName  , nSrcNameLen ); 
        if( bAppendExtension )
            strncpy( g_pDstName + nSrcNameLen, sExtension, nExtension );
        else
        {
            char *pDstExtension = strrchr( g_pDstName, '.' );
            if ( !pDstExtension )
                FATAL( "Couldn't decompress '%s'\n", pSrcName );
            
            // 1) check extension length is 4 chars
            // 2) check extension name is ".LZW"
            const size_t nDstLen = g_pDstName + nSrcNameLen - pDstExtension;
            if ((nDstLen != nExtension) || (strcmp( pDstExtension, sExtension ) != 0))
                FATAL( "Unknown %s extension: '%s'", sExtension, pDstExtension );

            *pDstExtension = 0; // truncate/remove extension 
        }

        FILE * pDstFile = fopen( g_pDstName, "w+b" );
        if ( pDstFile )
            g_pDstFile = pDstFile; // Note: OutFlush() will fclose()
    }
}

// ========================================================================
int main( int nArg, const char *aArg[] )
{
    CRC32_Init();

    if (nArg > 1) 
    {
        long   nLen = 0; // length of input filename
        size_t nSrc = 0; // length of input buffer 
        u8    *pSrc = NULL;

        for ( int iArg = 1; iArg < nArg; iArg++ )
        {
            const char *pArg = aArg[ iArg ];

            if ((strcmp( pArg, "-?" ) == 0) || (strcmp( pArg, "-h" ) == 0) || (strcmp( pArg, "--help") == 0))
            {
                FATAL(
"LZW Compress/Decompress.  'Mostly' compatible with 'compress'.\n"
"Usage: [-c] [-f] [-v]    file       (Output: file%s)\n"
"                 [-v] -d file%s   (Output: file)\n"
"   -c  Force output to console.     (Default: off)\n"
"   -f  Force compression.           (Default: Delete output if can't compress)\n"
"   -v  Verbose mode.  Print stats.  (Default: off)\n"
"   -d  Decompress.                  (Default: compress)\n"
                    , sExtension, sExtension );
            }
            else
            if (strcmp( pArg, "-v" ) == 0) // verbose 1 = stats
                g_nVerbose++;
            else
            if (strcmp( pArg, "-c" ) == 0) // force console output
                g_nFlags |= DST_FORCE_CONSOLE; // don't change g_pDstFile from stdout
            else
            if (strcmp( pArg, "-f" ) == 0) // force output of file regardless if can't compress
                g_nFlags |= DST_FORCE_COMPRESS;
            else
            if (strcmp( aArg[ iArg ], "-d" ) == 0) // decompress
            {
                if( ++iArg >= nArg)
                    break;
                pArg = aArg[ iArg ];
                nLen = strlen( pArg );
                File_ReadSrc( nLen, pArg, nSrc, pSrc );
                File_MakeDst( nLen, pArg, false );

                Decompress( nSrc, pSrc );
                OutFlush( pSrc );
            }
            else // compress
            {
                nLen = strlen( (const char*) pArg );
                File_ReadSrc( nLen, pArg, nSrc, pSrc );
                File_MakeDst( nLen, pArg, true );

                Compress( nSrc, pSrc );
                OutFlush( pSrc );
            }
        }
   }
   return 0;
}
